# Cancel Crawl
Source: https://docs.nimbleway.com/api-reference/crawl/cancel-crawl
/api-reference/openapi.json delete /v2/crawl/{id}
# Crawl by ID
Source: https://docs.nimbleway.com/api-reference/crawl/crawl-by-id
/api-reference/openapi.json get /v2/crawl/{id}
# Create Crawl
Source: https://docs.nimbleway.com/api-reference/crawl/create-crawl
/api-reference/openapi.json post /v2/crawl
# List Crawls
Source: https://docs.nimbleway.com/api-reference/crawl/list-crawls
/api-reference/openapi.json get /v2/crawl
# Check Domain Health
Source: https://docs.nimbleway.com/api-reference/domain-health/check-domain-health
/api-reference/openapi.json post /v1/domain-health/check
Check whether Nimble is seeing healthy traffic to one or more domains right now, sourced from real request outcomes across Search, SERP, ecommerce, and Extract traffic.
# Get Driver
Source: https://docs.nimbleway.com/api-reference/domain-knowledge/get-driver
/api-reference/openapi.json get /v2/domain-knowledge/driver
Resolves the suggested driver for a given URL or agent name. Exactly one of `url` or `agent` must be provided.
# Create Extract Template Generation
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/create-extract-template-generation
/api-reference/openapi.json post /v2/extract/templates/generations
# Delete Extract Template
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/delete-extract-template
/api-reference/openapi.json delete /v2/extract/templates/{extract_template_name}
# Get Extract Template
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/get-extract-template
/api-reference/openapi.json get /v2/extract/templates/{extract_template_name}
# Get Extract Template Generation
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/get-extract-template-generation
/api-reference/openapi.json get /v2/extract/templates/generations/{generation_id}
# Get Extract Template Version
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/get-extract-template-version
/api-reference/openapi.json get /v2/extract/templates/{extract_template_name}/versions/{version_id}
# List Extract Template Versions
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/list-extract-template-versions
/api-reference/openapi.json get /v2/extract/templates/{extract_template_name}/versions
# List Extract Templates
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/list-extract-templates
/api-reference/openapi.json get /v2/extract/templates
# Run Extract Template
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/run-extract-template
/api-reference/openapi.json post /v2/extract/templates/run
Execute Extract Template Realtime Endpoint
# Run Extract Template (Async)
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/run-extract-template-async
/api-reference/openapi.json post /v2/extract/templates/async
Execute Extract Template Async Endpoint
# Run Extract Template (Batch)
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/run-extract-template-batch
/api-reference/openapi.json post /v2/extract/templates/batch
Execute Extract Template Batch Endpoint
# Update Extract Template
Source: https://docs.nimbleway.com/api-reference/extract-templates-api/update-extract-template
/api-reference/openapi.json patch /v2/extract/templates/{extract_template_name}
# Extract
Source: https://docs.nimbleway.com/api-reference/extract/extract
/api-reference/openapi.json post /v2/extract
# Extract Async
Source: https://docs.nimbleway.com/api-reference/extract/extract-async
/api-reference/openapi.json post /v2/extract/async
# Extract Batch
Source: https://docs.nimbleway.com/api-reference/extract/extract-batch
/api-reference/openapi.json post /v2/extract/batch
# Introduction
Source: https://docs.nimbleway.com/api-reference/introduction
Get started with Nimble's API
Nimble API turns the live web into structured data through a simple REST API. Search the web, extract any page, run pre-built Extract Templates for popular platforms or generate one for any site, run Agents for cited research, map site structures, and crawl entire websites at scale.
**API v2.** This reference documents the v2 API surface. Pre-built extraction
lives at `/v2/extract/templates` (**Extract Templates** — see the [Templates
guide](/nimble-sdk/web-tools/extract/template)), and autonomous research at
`/v2/agents` (**Agents** — see the [Agent guide](/nimble-sdk/web-search-agents/overview)).
The legacy v1 endpoints keep working until they are deprecated, but new
integrations should target v2.
## OpenAPI Specification
The full OpenAPI (Swagger) specification is available for download. Use it to generate clients, import into tools like Postman, or feed it to coding agents.
Get the complete OpenAPI 3.1 specification at
[`/api-reference/openapi.json`](/api-reference/openapi.json).
## API Features
Extract clean HTML and structured data from any URL with custom parsing and
browser actions.
Run Agents for any website — pre-built for popular platforms, or
create your own.
Search the web and retrieve full content from top results with optional AI
summaries.
Discover all URLs on a website with metadata for planning crawl strategies.
Extract data from entire websites with async crawling across multiple pages.
Retrieve results from async operations (extract, search, map, crawl).
## Base URL
All API requests use the following base URL:
```bash theme={"system"}
https://sdk.nimbleway.com/v2
```
## Authentication
All requests require authentication using a Bearer token in the Authorization header:
```bash theme={"system"}
Authorization: Bearer YOUR_API_KEY
```
Get your API key from the [Nimble Dashboard](https://online.nimbleway.com/settings/api-keys).
### Example Request
```bash theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true
}'
```
## Response Codes
Nimble uses standard HTTP status codes to indicate the success or failure of requests.
| Status | Description |
| - | - |
| 200 | Request successful. |
| 400 | Bad request - check your parameters. |
| 401 | Unauthorized - invalid or missing API key. |
| 402 | Payment required - insufficient budget or trial expired. |
| 422 | Validation error - invalid request parameters. |
| 429 | Rate limit exceeded - slow down your requests. |
| 500 | Internal server error - Nimble infrastructure issue. |
## Rate Limits
Nimble enforces rate limits to ensure service stability and fair usage across all users.
### Default Rate Limits
| Driver Type | Rate Limit |
| - | - |
| `vx6`, `vx8`, `vx10` | 83 QPS (5,000 QPM) |
**QPS** = Queries Per Second, **QPM** = Queries Per Minute. Rate limits apply
per API key.
### Rate Limit Headers
Responses include headers showing your current rate limit status:
```bash theme={"system"}
ratelimit-limit: 5000
ratelimit-remaining: 4985
```
### Exceeding Rate Limits
When you exceed the rate limit, you'll receive a 429 status code with retry information:
```json theme={"system"}
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Current limit: 83 QPS",
"retry_after": 2
}
}
```
**Need higher limits?** Contact [sales@nimbleway.com](https://login.start-chat.com/modal/601e4da4-50a0-4dc7-8155-bb84f2952c40/27847549-dc04-4144-98a5-d40e568bf6af?magicLinkId=slFQ2j\&UID=65ab324c-ecde-4748-9f52-8e395054774b.1753267174019) for custom rate limits.
## Async Operations
Several endpoints support asynchronous processing for long-running operations:
* `/v2/extract/async` - Async data extraction
* `/v2/extract/templates/async` - Async template extraction
* `/v2/serp/async` - Async SERP retrieval
* `/v2/media/async` - Async media download
* `/v2/crawl` - Crawl operations (async only)
### Working with Async Tasks
1. **Start an async operation** - Returns a task ID
2. **Check task status** - Use `/v2/tasks/{id}` to check progress
3. **Retrieve results** - Get results from `/v2/tasks/{id}/results` when complete
```bash theme={"system"}
# Start async extraction
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{"url": "https://example.com"}'
# Returns: {"task_id": "8e8cfde8-345b-42b8-b3e2-0c61eb11e00f"}
# Check task status
curl 'https://sdk.nimbleway.com/v2/tasks/8e8cfde8-345b-42b8-b3e2-0c61eb11e00f' \
--header 'Authorization: Bearer YOUR_API_KEY'
# Get results when complete
curl 'https://sdk.nimbleway.com/v2/tasks/8e8cfde8-345b-42b8-b3e2-0c61eb11e00f/results' \
--header 'Authorization: Bearer YOUR_API_KEY'
```
## Next Steps
Make your first API request in minutes
Understand rate limits and technical specifications
# Cancel Run
Source: https://docs.nimbleway.com/api-reference/jobs/cancel-run
/api-reference/openapi.json post /v2/jobs/runs/{run_id}/cancel
# Create Job
Source: https://docs.nimbleway.com/api-reference/jobs/create-job
/api-reference/openapi.json post /v2/jobs
# Delete Job
Source: https://docs.nimbleway.com/api-reference/jobs/delete-job
/api-reference/openapi.json delete /v2/jobs/{job_id}
# Get Job
Source: https://docs.nimbleway.com/api-reference/jobs/get-job
/api-reference/openapi.json get /v2/jobs/{job_id}
# Get Run
Source: https://docs.nimbleway.com/api-reference/jobs/get-run
/api-reference/openapi.json get /v2/jobs/runs/{run_id}
# Get Run Artifact
Source: https://docs.nimbleway.com/api-reference/jobs/get-run-artifact
/api-reference/openapi.json get /v2/jobs/runs/{run_id}/artifacts/{artifact_id}
# Get Run Artifact Download URL
Source: https://docs.nimbleway.com/api-reference/jobs/get-run-artifact-download-url
/api-reference/openapi.json get /v2/jobs/runs/{run_id}/artifacts/{artifact_id}/download-url
# List Jobs
Source: https://docs.nimbleway.com/api-reference/jobs/list-jobs
/api-reference/openapi.json get /v2/jobs
# List Run Artifacts
Source: https://docs.nimbleway.com/api-reference/jobs/list-run-artifacts
/api-reference/openapi.json get /v2/jobs/runs/{run_id}/artifacts
# List Runs for Job
Source: https://docs.nimbleway.com/api-reference/jobs/list-runs-for-job
/api-reference/openapi.json get /v2/jobs/{job_id}/runs
# Preview Run Artifact
Source: https://docs.nimbleway.com/api-reference/jobs/preview-run-artifact
/api-reference/openapi.json get /v2/jobs/runs/{run_id}/artifacts/{artifact_id}/preview
# Trigger Run
Source: https://docs.nimbleway.com/api-reference/jobs/trigger-run
/api-reference/openapi.json post /v2/jobs/{job_id}/runs
# Update Job
Source: https://docs.nimbleway.com/api-reference/jobs/update-job
/api-reference/openapi.json patch /v2/jobs/{job_id}
# Map
Source: https://docs.nimbleway.com/api-reference/map/map
/api-reference/openapi.json post /v2/map
# Media Download
Source: https://docs.nimbleway.com/api-reference/media/media-download
/api-reference/openapi.json post /v2/media
Download media from a URL. Waits for the result before responding.
# Media Download Async
Source: https://docs.nimbleway.com/api-reference/media/media-download-async
/api-reference/openapi.json post /v2/media/async
Download media from a URL asynchronously. Returns a task ID immediately.
# Create Agent
Source: https://docs.nimbleway.com/api-reference/public-api/create-agent
/api-reference/openapi.json post /v2/agents
Create an Agent. Either pass `template` to materialize a pre-built template (its fields, goals, sources, and suggested questions are copied), or define the agent from scratch with `display_name`, `goals`, `sources`, and an optional `output_schema` for structured results.
# Create Agent Run
Source: https://docs.nimbleway.com/api-reference/public-api/create-agent-run
/api-reference/openapi.json post /v2/agents/{agent_id}/runs
Start an agent run. The run executes asynchronously: the response returns immediately with status `queued`, then poll `GET .../runs/{run_id}` until `completed` and fetch the output from `GET .../runs/{run_id}/result` — or set `enable_events: true` and follow `GET .../runs/{run_id}/events` for live progress.
To enrich existing records instead of researching from scratch, pass them in `input_data`; this requires an `output_schema` (on the request or the agent).
# Create Run
Source: https://docs.nimbleway.com/api-reference/public-api/create-run
/api-reference/openapi.json post /v2/agents/runs
Creates a minimal persistent Agent and starts a run for it. The response includes `web_search_agent_id` for later agent and run queries.
# Deactivate Agent
Source: https://docs.nimbleway.com/api-reference/public-api/deactivate-agent
/api-reference/openapi.json delete /v2/agents/{agent_id}
Deactivate an agent. This is a soft delete: the agent can no longer start new runs, but its existing runs and their results remain retrievable.
# Get Agent
Source: https://docs.nimbleway.com/api-reference/public-api/get-agent
/api-reference/openapi.json get /v2/agents/{agent_id}
Retrieve a single Agent by ID.
# Get Agent Run
Source: https://docs.nimbleway.com/api-reference/public-api/get-agent-run
/api-reference/openapi.json get /v2/agents/{agent_id}/runs/{run_id}
Retrieve a run's current state. Poll this endpoint after creating a run: the run is finished once `status` is `completed`, `failed`, or `cancelled`.
# Get Agent Run Result
Source: https://docs.nimbleway.com/api-reference/public-api/get-agent-run-result
/api-reference/openapi.json get /v2/agents/{agent_id}/runs/{run_id}/result
Fetch the output of a completed run. The `output` is `type: "text"` (a prose answer) or `type: "json"` (structured data matching the output schema), plus `trust` metadata with per-claim citations for the answer.
While the run is still `queued` or `running` this endpoint returns `409`; if the run `failed` or was `cancelled` it returns `422` with the run and error details.
# Get Agent Template
Source: https://docs.nimbleway.com/api-reference/public-api/get-agent-template
/api-reference/openapi.json get /v2/agents/templates/{template_name}
Retrieve a single agent template by its stable `template_name`.
# List Agent Runs
Source: https://docs.nimbleway.com/api-reference/public-api/list-agent-runs
/api-reference/openapi.json get /v2/agents/{agent_id}/runs
List the runs of an agent, newest first, paginated with `offset`/`limit`.
# List Agent Templates
Source: https://docs.nimbleway.com/api-reference/public-api/list-agent-templates
/api-reference/openapi.json get /v2/agents/templates
List the pre-built agent templates available to your account. Use a template's `template_name` with `POST /v2/agents` to create an agent instance from it.
# List Agents
Source: https://docs.nimbleway.com/api-reference/public-api/list-agents
/api-reference/openapi.json get /v2/agents
List the active Agents in your account. Results are scoped to the workspace resolved from your token (or the optional `workspace_id` query parameter) and paginated with `offset`/`limit`.
# Stream Agent Run Events
Source: https://docs.nimbleway.com/api-reference/public-api/stream-agent-run-events
/api-reference/openapi.json get /v2/agents/{agent_id}/runs/{run_id}/events
Stream a run's progress as [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (`text/event-stream`). Create the run with `enable_events: true` to have events published. A keep-alive comment is sent every 15 seconds.
# Update Agent
Source: https://docs.nimbleway.com/api-reference/public-api/update-agent
/api-reference/openapi.json patch /v2/agents/{agent_id}
Update an agent with a [JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) document — an array of `{op, path, value}` operations applied to the agent, e.g. `[{"op": "replace", "path": "/display_name", "value": "My agent"}]`. Returns the updated agent.
# Search
Source: https://docs.nimbleway.com/api-reference/search/search
/api-reference/openapi.json post /v2/search
# SERP
Source: https://docs.nimbleway.com/api-reference/serp/serp
/api-reference/openapi.json post /v2/serp
# SERP Async
Source: https://docs.nimbleway.com/api-reference/serp/serp-async
/api-reference/openapi.json post /v2/serp/async
# SERP Batch
Source: https://docs.nimbleway.com/api-reference/serp/serp-batch
/api-reference/openapi.json post /v2/serp/batch
# Get batch details
Source: https://docs.nimbleway.com/api-reference/tasks/get-batch-details
/api-reference/openapi.json get /v2/batches/{batch_id}
Retrieve the details of a batch including all its tasks and completion status.
# Get batch progress
Source: https://docs.nimbleway.com/api-reference/tasks/get-batch-progress
/api-reference/openapi.json get /v2/batches/{batch_id}/progress
Retrieve lightweight progress information for a batch without fetching all task details.
# List batches
Source: https://docs.nimbleway.com/api-reference/tasks/list-batches
/api-reference/openapi.json get /v2/batches
Retrieve a paginated list of batches for the authenticated account.
# List tasks
Source: https://docs.nimbleway.com/api-reference/tasks/list-tasks
/api-reference/openapi.json get /v2/tasks
Retrieve a paginated list of tasks for the authenticated account.
# Task Result
Source: https://docs.nimbleway.com/api-reference/tasks/task-result
/api-reference/openapi.json get /v2/tasks/{task_id}/results
Retrieve the results of a completed task.
# Task Status
Source: https://docs.nimbleway.com/api-reference/tasks/task-status
/api-reference/openapi.json get /v2/tasks/{task_id}
Retrieve the details of a specific task by its ID.
# Changelog
Source: https://docs.nimbleway.com/changelog/release-notes
Stay up to date with the latest Nimble SDK updates and improvements
Track new features, improvements, and updates to the Nimble SDK and Web Tools. We ship updates regularly to enhance performance, add capabilities, and improve your developer experience.
Have feedback or questions about a release? [Reach out to our
team](https://portal.usepylon.com/nimble)
### Release Notes for October, 2026
### New Features
#### Extract file parsing
Extract can now parse PDFs, Word documents, spreadsheets, and presentations into markdown. Add `file_parsing_options` to an Extract request and read the result from `data.markdown`.
* **`mode`**: `light` for fast, text-native extraction, or `pro` for LLM-based parsing with OCR fallback on scanned and table-heavy PDFs.
* **`max_pages`**: rejects PDFs over this page count before parsing.
File parsing is billed per page parsed on top of the base Extract price: \$2.50 per 1,000 pages for `light` and \$7.50 per 1,000 pages for `pro` at list price.
[View Documentation](/nimble-sdk/web-tools/extract/features/file-parsing)
### Release Notes for August, 2026
### Improvements
#### Web Search Agent output schema validation
Custom and generated Web Search Agents now enforce hard limits on `output_schema` at the API boundary. Invalid schemas return **422** with actionable errors instead of failing at run time.
Supported roots: an object with `properties`, or an array of objects. Rejected patterns include `format`, `pattern`, min/max constraints, root `anyOf`, standalone `"type": "null"`, nesting deeper than 5 levels, and more than 100 total properties.
[View output schema limits](/nimble-sdk/web-search-agents/use-cases/dataset-building#output-schema-limits)
### Integrations
#### Vercel eve
Nimble is now an extension for [eve](https://eve.dev), Vercel's framework for durable AI agents. The [`@nimble-way/eve`](https://www.npmjs.com/package/@nimble-way/eve) package (v0.1.0) mounts Search, Extract, and Web Search Agent runs in one line, and can replace eve's built-in `web_search` and `web_fetch` so every agent turn uses Nimble.
[View Documentation](/integrations/connectors/vercel-eve)
### Release Notes for July, 2026
### New Features
#### API v2 — Extract Templates & Web Search Agents
Nimble's API is now **v2**. The former "agents" surface splits into two clearly separated products, each with its own endpoints and docs.
* **Extract Templates** (`/v2/extract/templates/*`) — pre-built, Nimble-maintained templates that turn popular sites like Amazon, Google, and Walmart into clean, structured JSON. Pick a template, pass a few parameters, and get production-ready data at scale — no selectors, no scraping expertise. Nimble maintains each template 24/7 and auto-heals when target sites change.
* **Web Search Agents** (`/v2/agents/*`) — a new autonomous research API. Ask a question and get back finished, cited research with a trust report — not just links.
* **Jobs API v2** (`/v2/jobs/*`) and **Tasks & Batches v2** complete the v2 surface.
```bash theme={"system"}
# Run a pre-built Extract Template
curl -X POST https://sdk.nimbleway.com/v2/extract/templates/run \
-H "Authorization: Bearer YOUR-API-KEY" \
-d '{"template": "amazon_pdp", "params": {"asin": "B08N5WRWNW"}}'
# Ask a Web Search Agent
curl -X POST https://sdk.nimbleway.com/v2/agents/runs \
-H "Authorization: Bearer YOUR-API-KEY" \
-d '{"input": "Compare the pricing of Datadog and Grafana Cloud."}'
```
[Extract Templates](/nimble-sdk/web-tools/extract/template) · [Web Search Agents](/nimble-sdk/web-search-agents/overview)
### Breaking Change
#### `/v1/agents` is now Extract Templates
The legacy pre-built agents at `/v1/agents/run` have moved to Extract Templates at `/v2/extract/templates/run`. The `agent` field is now `template`.
v1 endpoints keep working during a grace period — migrate to `/v2/extract/templates/*` at your own pace. See the [API Reference](/api-reference/introduction) for the full v1 → v2 mapping. "Web Search Agents" now refers only to the new research API under `/v2/agents/*`.
### Integrations
#### LlamaIndex
Nimble web search is now a first-class LlamaIndex tool via the [`llama-index-tools-nimble`](https://pypi.org/project/llama-index-tools-nimble/) package (v0.1.0). Call `NimbleToolSpec.search()` directly or hand it to a `FunctionAgent`, and feed the citable `Document` output into a `VectorStoreIndex` for RAG.
[View Documentation](/integrations/connectors/llamaindex)
#### Vercel AI SDK — Extract
The `@nimble-way/ai-sdk` connector (v0.2.1) now ships an **Extract** tool (`nimbleExtract`) alongside web search — read full page content, or chain search-then-read in a single flow.
[View Documentation](/integrations/connectors/vercel-ai-sdk)
#### Snowflake Native App
The Nimble Native App on Snowflake now covers both Search and Agents, adds App Builder Skills (including CMO Intelligence), and ships Marketplace install and setup guidance.
[View Documentation](/integrations/partnerships/snowflake/native-app)
### Improvements
#### Extract — automatic driver selection
Extract can now pick the optimal driver for each target automatically, so you get the best success rate without manually configuring driver tiers.
[View Documentation](/nimble-sdk/web-tools/extract/quickstart)
#### Search moves front and center
Search is now the first product under Web Tools. The legacy `include_answer` option has been removed from the Search API.
[View Documentation](/nimble-sdk/web-tools/search)
### Release Notes for June, 2026
### New Features
#### Jobs API
Drive Jobs programmatically, end to end. Create jobs, trigger and cancel runs, and list, preview, or download run artifacts over REST — everything the Nimble Platform UI does, now from code or CI/CD.
* **Cloud I/O** — read inputs from and deliver results to your own **Amazon S3** bucket, or a **Databricks** Unity Catalog table via Delta Sharing.
* **Any format** — JSON, CSV, or Parquet, in and out.
[View Documentation](/nimble-sdk/agentic/jobs)
### Integrations
#### Databricks
A full Databricks integration suite landed this month:
* **Data Enrichment** — call Nimble as SQL table functions to enrich tables directly in your warehouse.
* **Genie** — query the live web in natural language through Nimble MCP, plus a **Genie Code** skill (`nimble-search`) for notebooks.
* **Governance** — route Nimble through the Unity AI Gateway for centralized policy and audit.
[View Documentation](/integrations/partnerships/databricks)
#### More native web-search providers
Nimble is now available as a native web-search provider in [OGX](/integrations/connectors/ogx) and [Omnigent](/integrations/connectors/omnigent).
[View Documentation](/integrations/connectors/ogx)
### Documentation
#### OpenAPI spec now public
The full Nimble OpenAPI spec is now served under the API Reference, so agents and codegen tools can consume it directly.
[View Documentation](/api-reference/introduction)
### Release Notes for May, 2026
### New Features
#### Domain Knowledge API
Look up the recommended driver and detected antibot systems for any domain or agent before running at scale. Returns the driver tier, a list of detected antibots (e.g. `cloudflare`, `perimeterx`, `akamai`), and whether JavaScript rendering is required.
```bash theme={"system"}
GET /v1/domain-knowledge/driver?url=www.lowes.com
GET /v1/domain-knowledge/driver?agent=target_serp
```
[View Documentation](/nimble-sdk/web-tools/domain-knowledge)
### New Features
#### Media Download API
Download images, video, audio, and documents directly via the Nimble SDK. Two modes available: **Realtime** for instant binary downloads, and **Async** for large files delivered to your storage bucket.
```json theme={"system"}
{
"url": "https://example.com/image.jpg",
"country": "US"
}
```
[View Documentation](/nimble-sdk/web-tools/media)
### New Features
#### Jobs
Run any Web Search Agent as a managed, recurring workload — no orchestration code required. Point a job at an agent, upload your input list (CSV, Parquet, or JSON), set a schedule, and Nimble fans out one agent invocation per row on every tick.
* **Any scale** — thousands to millions of inputs per run, with concurrency and retries handled automatically
* **Flexible scheduling** — manual trigger, daily, weekly, monthly, or a custom cron expression
* **Structured output** — results assembled into a single JSON, CSV, or Parquet file per run
* **Run monitoring** — per-run success rate, completeness score, and error samples in the Nimble Platform UI
Available now at [online.nimbleway.com/jobs](https://online.nimbleway.com/jobs).
[View Documentation](/nimble-sdk/agentic/jobs)
### Integrations
#### Azure MCP Center
Nimble MCP is now an officially onboarded partner on [Azure MCP Center](https://mcp.azure.com/detail/nimble-mcp) — Microsoft's curated registry of enterprise-ready MCP servers. Install directly into VS Code with one click, authenticate via OAuth 2.1 (no API key to paste), and use Nimble's full web data toolkit in GitHub Copilot Agent Mode.
* One-click install from the Azure MCP Center listing
* OAuth 2.1 sign-in with PKCE — tokens managed and refreshed automatically by VS Code
* Available to all Azure API Center enterprise registries automatically
[View Documentation](/integrations/partnerships/microsoft/azure-mcp-center)
### New Features
#### SERP API
Submit search engine queries and receive structured results. Supports Google Search, Google Images, Google News, Google Maps (search, place, reviews), and Google AIO.
```json theme={"system"}
{
"search_engine": "google_search",
"query": "nimble web data",
"country": "US",
"locale": "en"
}
```
Async and Batch endpoints are also available — submit up to 1,000 queries in a single request using `inputs` + `shared_inputs`.
[View Documentation](/nimble-sdk/web-tools/serp)
### Release Notes for April, 2026
### New Skills
#### Web Search Skills for Agent Skills
New verticalized web search skills for AI coding assistants, organized into four categories:
* **[Business Research](/integrations/agent-skills/web-search-skills/business-research)** -- Company Deep Dive (sourced 360° company reports), Competitor Intel (news, funding, hiring, product launches), Market Finder (discover businesses in any market or geography)
* **[Marketing](/integrations/agent-skills/web-search-skills/marketing)** -- Competitor Positioning (messaging, pricing, CTAs, content gaps with before/after tracking)
* **[Productivity](/integrations/agent-skills/web-search-skills/productivity)** -- Meeting Prep (attendee and company research before any meeting), Local Places (discover, enrich, and score local businesses in any neighborhood)
* **[SEO](/integrations/agent-skills/web-search-skills/seo)** -- SEO Intel (keyword research, rank tracking, site audits, content gaps, competitor keywords, AI visibility, GitHub SEO)
Install via the [Nimble plugin](/integrations/agent-skills/plugin-installation) for Claude Code or Cursor.
[View Documentation](/integrations/agent-skills/web-search-skills/overview)
### New Features
#### Agents API: Pagination Support
Run agents with pagination to collect results across multiple pages. Call the agent once per page, incrementing the pagination field (commonly `page`) in `params`.
```json theme={"system"}
{
"agent": "amazon_serp",
"params": { "keyword": "iphone 17", "page": 1 }
}
```
[View Documentation](/nimble-sdk/web-tools/extract/template#pagination)
### Integrations
#### Databricks Marketplace
Nimble MCP Server is now available on the [Databricks Marketplace](https://marketplace.databricks.com/details/1fe5e521-e9ef-49d8-97c4-b8c0a448ce15/Nimble_Nimble-MCP-Agentic-Web-Search-Platform) as a one-click install. Creates a Unity Catalog connection that gives Databricks agents, AI Playground, and notebooks direct access to Nimble's search, extract, map, and crawl tools.
[View Documentation](/integrations/partnerships/databricks/marketplace-mcp)
### Documentation
#### Extract API: Advanced Options
New Advanced Options page documenting low-level request controls for the Extract API. Covers `referrer_type`, `expected_status_codes`, `http2`, `session`, `tag`, `device`, and `render_options` (with nested fields for render type, timeout, iframe handling, resource blocking, and retry behavior).
[View Documentation](/nimble-sdk/web-tools/extract/features/advanced-options)
### New Features
#### Custom Agent Creation via API, SDK, and CLI
Create custom extraction agents programmatically. Describe what data you need, provide a sample URL, and Nimble generates a production-ready agent. Iterate with natural language prompts to refine the output. Available via REST API (`POST /v1/agents/generations`), Python/Node SDKs, and CLI.
[View Documentation](/nimble-sdk/web-tools/extract/template)
### New Features
#### New Links Format
New `links` format option for both the **Extract API** and **Agents API**. Pass `"links"` in the `formats` array to extract all URLs found on the page. Links are returned as an array of URL strings under `data.links`.
```json theme={"system"}
"formats": ["html", "links"]
```
```json theme={"system"}
"data": {
"links": [
"https://www.example.com/about",
"https://www.example.com/contact",
"https://external-site.com/resource"
]
}
```
[Extract Documentation](/nimble-sdk/web-tools/extract/features/formats) | [Templates Documentation](/nimble-sdk/web-tools/extract/template)
### Release Notes for March, 2026
### New Features
#### Agents API: Batch Endpoint
Submit up to **1,000 agent requests in a single request** with `POST /v1/agents/batch`. Each input runs as an independent async task. Use `shared_inputs` to set the agent and common delivery settings, and override per item in `inputs` as needed.
```json theme={"system"}
{
"inputs": [
{ "params": { "keyword": "iphone 15" } },
{ "params": { "keyword": "iphone 16" } }
],
"shared_inputs": { "agent": "amazon_serp" }
}
```
[View Documentation](/nimble-sdk/web-tools/extract/template#batch)
### New FeaturesBreaking Change
#### Agents API: Formats Parameter
New `formats` parameter for `POST /v1/agents/run` and `POST /v1/agents/async`. Pass `"html"`, `"markdown"`, or `"headers"` to include additional data alongside `data.parsing`.
```json theme={"system"}
"formats": ["html", "markdown"]
```
**Breaking change:** `data.html` is no longer returned by default. To continue receiving HTML in the response, add `"html"` to the `formats` array.
**Before:**
```json theme={"system"}
{ "agent": "amazon_pdp", "params": { "asin": "B08N5WRWNW" } }
// → data.html was included automatically
```
**After:**
```json theme={"system"}
{
"agent": "amazon_pdp",
"params": { "asin": "B08N5WRWNW" },
"formats": ["html"]
}
// → data.html included only when explicitly requested
```
[View Documentation](/nimble-sdk/web-tools/extract/template)
### New FeaturesBreaking Change
#### Extract API: New Headers Format
New `headers` format option. Pass `"headers"` in the `formats` array to include the HTTP response headers returned by the server. Headers are returned as a key-value object under `data.headers`.
```json theme={"system"}
"formats": ["html", "headers"]
```
**Breaking change:** Headers are no longer returned by default. Previously, `data.headers` was included in every response automatically. To continue receiving headers, add `"headers"` to your `formats` array.
**Before:**
```json theme={"system"}
{ "url": "https://example.com" }
// → data.headers was included automatically
```
**After:**
```json theme={"system"}
{ "url": "https://example.com", "formats": ["html", "headers"] }
// → data.headers included only when explicitly requested
```
[View Documentation](/nimble-sdk/web-tools/extract/features/formats)
### New Features
#### Batch Extract API
Submit up to **1,000 URLs in a single request** with `POST /v1/extract/batch`. Each URL runs as an independent async task. Use `shared_inputs` to set common settings (render, country, cloud delivery) across the batch, and override per item in `inputs` as needed.
[View Documentation](/nimble-sdk/web-tools/extract/features/async)
### Integrations
#### Integration Connectors
New connector guides for plugging Nimble web tools into popular AI frameworks and platforms:
* [**Smithery**](/integrations/connectors/smithery) — Connect via the Smithery MCP registry with AI SDK and TypeScript examples.
* [**OpenAI**](/integrations/connectors/openai) — Use Nimble with OpenAI function calling and the Agents SDK.
* [**Anthropic**](/integrations/connectors/anthropic) — Integrate with Claude tool-use API and Tool Runner.
* [**Google ADK**](/integrations/connectors/google-adk) — Use Nimble with Google ADK agents via MCP.
* [**LangChain**](/integrations/connectors/langchain) — LangChain tools and retrievers for Nimble web data.
### Documentation
#### Nimble Docs MCP
Ask questions about Nimble directly from Claude, Cursor, VS Code, or any MCP-compatible tool. The Nimble Docs MCP server exposes the full documentation knowledge base including API references, guides, and code examples, all via the `search_nimble_docs` tool. No API key required.
**Server URL:** `https://docs.nimbleway.com/mcp`
[View Documentation](/integrations/mcp-server/mcp-docs)
### Release Notes for February, 2026
### New Features
#### GET Agents API
New endpoints for discovering and exploring available web search agents. List all available agent templates with `GET /agents` or retrieve detailed information about a specific agent including its input/output schema with `GET /agents/{agent_name}`. \
Perfect for building dynamic agent selections or understanding agent capabilities programmatically. [View Documentation](/nimble-sdk/agentic/agent-gallery)
### New Features
#### Screenshot Format
New `screenshot` format option for the Extract API. Capture full-page screenshots as base64-encoded PNG images for visual verification, monitoring, and archival. Screenshots automatically enable JavaScript rendering (VX8/VX10 driver). [View Documentation](/nimble-sdk/web-tools/extract/features/formats)
### Documentation
#### Documentation Updates
* **Callbacks & Delivery Guide**: Comprehensive guide covering all async result delivery options - polling, webhooks, and cloud delivery to S3/GCS. Includes setup instructions for bucket permissions. [View Guide](/nimble-sdk/admin/callbacks-and-delivery)
* **Integration Documentation**: New guide covering all available integration and AI connectors to Nimble SDK capabilities. [View Integrations](/integrations)
### New Features
#### Agent Skills API
Advanced web search skills powered by Nimble Search API. Built on the open-source [Agent Skills](https://agentskills.io/) standard for cross-platform agent compatibility, connected to you Claude, Cursor and more.
Features 8 specialized focus modes (general, coding, news, academic, shopping, social, geo, location) with AI-powered answer generation and smart result filtering. [Try Now](/integrations/agent-skills/web-tools-skills/nimble-web-expert)
### Release Notes for January, 2026
### Documentation
#### Comprehensive Guides
* **Quickstart Pages**: New quickstart guides for all Web Tools with installation steps, basic usage examples, common patterns, and next steps. Get started in under 5 minutes.
* **Usage Documentation**: Detailed usage pages for each Web Tool covering all parameters, response formats, best practices, common use cases, and limitations.
* **API Reference**: Complete API reference with request/response schemas, error codes, rate limits, and authentication methods for all endpoints. [Try Now](/api-reference/introduction)
* **Account Management**: New guide covering API key generation, team member management, permissions, and security best practices. [View Guide](/nimble-sdk/admin/account-management)
* **Integration Documentation**: New guide covering all available integration and AI connectors to Nimble SDK capabilities. [View Integrations](/integrations)
### New Features
#### Extract API
The `/extract` retrieves clean HTML, text, and structured data from any webpage. Supports JavaScript rendering, stealth mode for anti-bot protection, custom parsing with CSS selectors, browser actions, screenshots, and network request capture. [Try Now](/nimble-sdk/web-tools/extract/quickstart)
### New Features
#### Web Search Agents API
The `/agent` are ready-to-use web search agent extractors for popular websites like Amazon, Google, LinkedIn, and hundreds more. No coding or CSS selectors required - just provide the template name, and template inputs such as search term or product ID and get structured data instantly. Agents are maintained 24/7 by Nimble and automatically updated when sites change. [Try Now](/nimble-sdk/web-tools/extract/template)
### New Features
#### Search API
The `/search` performs web searches and retrieves parsed content from top results. Supports three search depth modes (`lite`, `fast`, `deep`) for flexible cost/quality tradeoffs, and AI-powered answer summaries from search results. [Try Now](/nimble-sdk/web-tools/search)
### New Features
#### Map API
The `/map` perform fast URL discovery and site structure mapping. Ideal for discovering all pages on a website, extracting sitemap URLs, and understanding site architecture without deep crawling overhead. Supports subdomain filtering and exact domain matching. [Try Now](/nimble-sdk/web-tools/map)
### New Features
#### Crawl API
The `/crawl` perform deep website crawling for comprehensive data extraction across multiple pages. Features smart navigation, data normalization, and multi-page scraping with automatic traversing handling. [Try Now](/nimble-sdk/web-tools/crawl)
# Build Your First Extract Template
Source: https://docs.nimbleway.com/guides/build-first-agent-tutorial
Give Claude one prompt and let the Nimble plugin handle Extract Template discovery, custom template generation, and script writing, end to end
## What You'll Build
A Python script that pulls running shoe data from **Amazon**, **Walmart**, and **Nike.com** and outputs a ranked, cross-retailer price comparison — all from a single prompt to your AI coding assistant.
This tutorial shows the core power of Nimble: **Extract Templates work with any website**, not just the ones in the gallery.
The Nimble plugin handles the rest:
* **Finds** pre-built Extract Templates for sites already in the gallery (Amazon, Walmart)
* **Generates** a custom template on the fly for Nike.com, a site with no pre-built template
* **Writes** the full analysis script using the templates it collected
## Prerequisites
Sign up free and grab your API key from Settings → API Keys
Install the Nimble plugin for Claude Code or Cursor
You'll also need Python 3.8+ and the Nimble SDK:
```bash theme={"system"}
pip install nimble_python
```
***
## Step 1: Give Claude the Prompt
Open a new session in Claude Code (or Cursor) and paste this prompt:
```
Build a Python script that runs competitive analysis on running shoes across
Amazon, Walmart, and Nike.com. Use Nimble for all data collection.
For each retailer:
- Search the Nimble Extract Template gallery for an existing template. Use it if one exists.
- If no template exists for that site, use Nimble to generate a custom
Extract Template, then publish it for reuse.
The script should collect product name, price, rating (if available), and URL
from each source, then print a unified list sorted by price and save the full
results to shoe_analysis.json.
My Nimble API key is set as the NIMBLE_API_KEY environment variable.
```
```
Build a Python script that runs competitive analysis on running shoes across
Amazon, Walmart, and Nike.com. Use Nimble for all data collection.
For each retailer:
- Search the Nimble Extract Template gallery for an existing template. Use it if one exists.
- If no template exists for that site, use Nimble to generate a custom
Extract Template, then publish it for reuse.
The script should collect product name, price, rating (if available), and URL
from each source, then print a unified list sorted by price and save the full
results to shoe_analysis.json.
My Nimble API key is set as the NIMBLE_API_KEY environment variable.
```
That's it. Claude takes it from here.
***
## What Claude Does Next
Claude works through the task autonomously using the Nimble plugin. Here's what happens under the hood:
Claude calls `nimble_extract_templates_list` with the query `"amazon"` and finds `amazon_serp` — a pre-built Extract Template for Amazon search results. It inspects the schema and confirms `keyword` is the required input.
Same flow for Walmart — finds `walmart_search`, confirms it takes a `keyword` param and returns product name, price, and rating.
No public Extract Template exists for `nike.com`. Claude calls `nimble_extract_templates_generate` with a description of what's needed, waits for the template to be created, runs a test extraction, and publishes it as `nike_running_shoes_plp`.
With all three templates confirmed, Claude writes `shoe_analysis.py` using the Nimble Python SDK and the template names it just collected.
The script Claude produces will look something like this:
```python theme={"system"}
import json
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
KEYWORD = "running shoes"
NIKE_URL = "https://www.nike.com/w/mens-running-shoes"
def fetch_amazon():
result = nimble.extract.templates.run(
template="amazon_serp",
params={"keyword": KEYWORD}
)
products = result.to_dict()["data"]["parsing"]
return [
{
"source": "Amazon",
"name": p.get("product_name"),
"price": p.get("price"),
"rating": p.get("average_rating"),
"url": p.get("product_url"),
}
for p in products
if p.get("price")
]
def fetch_walmart():
result = nimble.extract.templates.run(
template="walmart_search",
params={"keyword": KEYWORD}
)
products = result.to_dict()["data"]["parsing"]
return [
{
"source": "Walmart",
"name": p.get("product_name"),
"price": p.get("price"),
"rating": p.get("rating"),
"url": p.get("product_url"),
}
for p in products
if p.get("price")
]
def fetch_nike():
result = nimble.extract.templates.run(
template="nike_running_shoes_plp",
params={"url": NIKE_URL}
)
products = result.to_dict()["data"]["parsing"]
return [
{
"source": "Nike",
"name": p.get("product_name"),
"price": p.get("price"),
"colors": p.get("colors", []),
"url": p.get("url"),
}
for p in products
if p.get("price")
]
def run_analysis():
print(f'Competitive analysis: "{KEYWORD}"\n')
all_products = []
print("Fetching Amazon...")
all_products.extend(fetch_amazon())
print("Fetching Walmart...")
all_products.extend(fetch_walmart())
print("Fetching Nike...")
all_products.extend(fetch_nike())
sorted_products = sorted(all_products, key=lambda x: x["price"])
print(f"\n{'=' * 55}")
print(f" RESULTS — {len(all_products)} products across 3 retailers")
print(f"{'=' * 55}\n")
for i, p in enumerate(sorted_products[:15], 1):
stars = f" ★{p['rating']}" if p.get("rating") else ""
colors = f" ({len(p['colors'])} colors)" if p.get("colors") else ""
print(f"{i:2}. [{p['source']:7}] ${p['price']:<8.2f} {p['name']}{stars}{colors}")
with open("shoe_analysis.json", "w") as f:
json.dump(all_products, f, indent=2)
print(f"\nFull results saved to shoe_analysis.json")
if __name__ == "__main__":
run_analysis()
```
The exact template names (e.g. `nike_running_shoes_plp`) are chosen by the skill at generation time. Claude will use whatever names were returned and write the script accordingly — you don't need to track them manually.
***
## Step 2: Run the Script
```bash theme={"system"}
python shoe_analysis.py
```
Expected output:
```
Competitive analysis: "running shoes"
Fetching Amazon...
Fetching Walmart...
Fetching Nike...
=======================================================
RESULTS — 62 products across 3 retailers
=======================================================
1. [Walmart ] $19.98 Athletic Works Men's Knit Running Shoe ★4.1
2. [Walmart ] $29.98 Starter Men's Athletic Running Shoe ★4.0
3. [Walmart ] $44.97 Skechers Go Run Consistent ★4.3
4. [Amazon ] $54.99 New Balance Men's 515 V3 Sneaker ★4.5
5. [Walmart ] $54.98 Nike Downshifter 12 ★4.5
6. [Amazon ] $64.99 Adidas Men's Runfalcon 3.0 ★4.6
7. [Amazon ] $69.99 Nike Men's Revolution 7 ★4.6
8. [Nike ] $110.00 Nike Pegasus Trail 5 (3 colors)
9. [Amazon ] $119.95 Brooks Ghost 15 ★4.7
10. [Nike ] $130.00 Nike Pegasus 41 (5 colors)
...
Full results saved to shoe_analysis.json
```
***
## What's Next
Need help choosing the right approach or scaling up?
View plans and subscribe
# Cookbooks
Source: https://docs.nimbleway.com/guides/cookbooks
# Nimble Documentation
Source: https://docs.nimbleway.com/home
Create Agents for any website. Extract structured, real-time knowledge for AI applications, data pipelines, and analytics — no scraping expertise required.
# Build with Nimble
Agents that turn any website into structured knowledge for AI
Agents research the live web and answer with receipts --> Ask like you'd brief an analyst, get a cited answer with a trust report
Input
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
run = nimble.agents.run(
input="Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const run = await nimble.agents.run({
input: "Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
});
```
```go Go theme={"system"}
package main
import (
"context"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
run, err := client.Agents.Run(context.Background(), nimble.AgentRunParams{
Input: "Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
})
_, _ = run, err
}
```
```bash CLI theme={"system"}
nimble agents run \
--input "Compare the pricing of Datadog, Grafana Cloud, and New Relic."
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents/runs' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"input": "Compare the pricing of Datadog, Grafana Cloud, and New Relic."
}'
```
Output
```json theme={"system"}
{
"output": {
"type": "text",
"content": "Datadog anchors the premium end of the
market, with Pro pricing from $15 per host [1].
Grafana Cloud undercuts it with a free tier [2]...",
"trust": {
"confidence": "high",
"claims": [
{
"id": 1,
"confidence": "high",
"reasoning": "Backed by a primary source (official)",
"citations": [
{ "url": "https://datadoghq.com/pricing", ... }
]
},
... and more
]
}
}
}
```
# Plugin Installation
Source: https://docs.nimbleway.com/integrations/agent-skills/plugin-installation
Install the Nimble plugin and turn your AI coding assistant into a web data expert
Install the Nimble plugin and turn Claude Code or Cursor into a web data powerhouse. Search, extract, scrape, and build reusable Extract Templates, all from your AI coding assistant.
The plugin bundles the Nimble skills and an MCP server connection into a single package.
## What's Included
| Component | Description |
| - | - |
| **nimble-web-expert** skill | Get live web data instantly. Search, extract, map, and crawl via Nimble CLI. Also runs Extract Templates and Agents |
| **MCP server config** | Pre-configured connection to the Nimble MCP server |
## Skills Overview
**Get web data immediately.** Fast, one-off extractions. Scrape pages, fetch URLs, search the web, crawl site sections. This is the only way your AI coding assistant can access live websites.
**Triggers:** Fetching any URL, scraping data, web search, bulk crawling, running Extract Templates and Agents
## Prerequisites
1. **Nimble CLI**: Install globally:
```bash theme={"system"}
npm i -g @nimble-way/nimble-cli
```
2. **Nimble API Key**: [Sign up](https://online.nimbleway.com/signup) and generate a key from your [Settings → API Keys](/nimble-sdk/admin/account-management#api-keys)
3. Set the environment variable:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key-here"
```
Or add to `~/.claude/settings.json` (Claude Code):
```json theme={"system"}
{
"env": {
"NIMBLE_API_KEY": "your-api-key-here"
}
}
```
## Install by Platform
Installs the Nimble skills and configures the MCP server.
```bash theme={"system"}
claude plugin marketplace add Nimbleway/agent-skills && \
claude plugin install nimble@nimble-plugin-marketplace
```
Run `/mcp` and confirm `nimble` appears in your connected servers.
### Alternative: local plugin directory
Clone the repo and load it as a local plugin:
```bash theme={"system"}
git clone https://github.com/Nimbleway/agent-skills.git
claude --plugin-dir /path/to/agent-skills
```
One click, from the Anthropic and Partners directory inside Claude.
Approve the scopes in your browser. There is no API key to paste or manage.
### Step-by-step, with screenshots
In a new Cowork task, click the **+** button, hover **Plugins**, then click **Manage plugins**.
Alternatively, from the **Customize** screen, click **+** next to **Personal plugins** and choose **Browse plugins**.
Switch to the **Plugins** tab, select **Anthropic & Partners**, and search for `Nimble`.
Open the Nimble plugin and click **Install**. The plugin's skills, agents, and MCP connector register automatically.
Nimble now appears under **Personal plugins** in Customize, with **Skills**, **Connectors**, and **Agents** sub-sections.
Open **Connectors**, then click **Connect** to start OAuth.
Review the permissions Claude is requesting and click **Approve** to authenticate with your Nimble workspace.
Once approved, the Nimble skills, agents, and MCP tools are live across your Cowork conversations.
For more on connecting Nimble to other Claude surfaces, see the [Claude AI Connectors guide](/integrations/connectors/anthropic/claude-connectors).
One click, then confirm the install in Cursor.
Then replace `NIMBLE_API_KEY` in **Settings → MCP Servers → nimble-mcp-server** with your [Nimble API key](https://online.nimbleway.com/settings/api-keys). The deeplink ships a placeholder, not a key.
Teaches the agent which Nimble product fits a given job.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a cursor
```
Open Settings, then MCP, and confirm `nimble-mcp-server` is listed.
Add to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
```json theme={"system"}
{
"mcpServers": {
"nimble-mcp-server": {
"url": "https://mcp.nimbleway.com/mcp",
"headers": {
"Authorization": "Bearer NIMBLE_API_KEY"
}
}
}
}
```
Restart Cursor for changes to take effect.
The `npx skills add` step above also copies the Cursor rules into your project.
Clone the repo and open the `agent-skills` folder in Cursor:
```bash theme={"system"}
git clone https://github.com/Nimbleway/agent-skills.git
```
Cursor auto-discovers:
* `skills/` the Nimble skills
* `rules/` Cursor rules (auto-loaded)
* `mcp.json` MCP server connection
Click **Plugins** in the Codex left menu.
Search for `Nimble` in **Plugins** and open it.
Click **Install plugin**. This installs the Nimble skills and the hosted MCP connection together.
**Codex is requesting access** opens in your browser. Click **Approve**, then authenticate with your Nimble workspace. There is no API key to paste or manage.
Codex asks to search the web and extract data from websites, create and manage Agents, maintain access when offline, and verify identity.
Bundled skills register on a new session. Start a new task, type `@nimble`, and select **Nimble**. A Nimble chip appears on the prompt, and everything you send in that task can reach the live web.
Adding the Nimble repository as a marketplace source installs the same packaged plugin. Replace the **Find Nimble** step with these two, then continue from **Install Nimble**.
1. In **Plugins**, click **Add** at the top right, then **Add a marketplace**.
2. In **Add plugin marketplace**, paste this into **Source**, then click **Add marketplace**:
```text theme={"system"}
https://github.com/Nimbleway/agent-skills.git
```
Leave **Git ref** and **Sparse paths** empty. The repository is public, so no GitHub credentials and no local clone are needed.
Codex stores MCP configuration in `~/.codex/config.toml`, or a project-scoped `.codex/config.toml`. Add:
```toml theme={"system"}
[mcp_servers.nimble-mcp-server]
url = "https://mcp.nimbleway.com/mcp"
bearer_token_env_var = "NIMBLE_API_KEY"
```
Codex reads `NIMBLE_API_KEY` from your environment at runtime, so the key is never written to the config file.
Installs the Nimble skills into Codex's skill discovery paths (`~/.agents/skills/`).
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a codex
```
Run `/mcp` inside the Codex TUI to confirm the server, and `/skills` to confirm the skills. Restart Codex if they do not appear.
### Verify with an Agent
Works the same in Desktop and CLI. A `/mcp` check proves the connection. To prove Nimble actually reaches the live web, run a research prompt that has to cite its sources:
```text theme={"system"}
Full 2026 report on best AI Frameworks for building agents.
Include the source URL for every claim.
```
In Desktop, attach **Nimble** with `@` first so Codex routes the prompt to the plugin. A correct result is an [Agent](/nimble-sdk/web-search-agents/overview) run: Nimble reads current pages and returns findings where every claim carries the URL it came from. If the answer arrives with no URLs, the model answered from memory and Nimble is not being used.
### Troubleshooting
| Symptom | Where | Cause | Action |
| - | - | - | - |
| The marketplace source is rejected | Desktop | A typo in the URL, or no network route to github.com | Confirm the repo is reachable with `git ls-remote https://github.com/Nimbleway/agent-skills.git` |
| `@nimble` returns no matches | Desktop | Bundled skills register only on a new session | Start a new task, or restart Codex, then type `@nimble` again |
| The OAuth window never opens, or never returns | Desktop | A blocked popup, or no route to `mcp.nimbleway.com` | Allow popups for Codex and retry. Check connectivity with `curl -I https://mcp.nimbleway.com/mcp` |
| `/mcp` shows Nimble as not connected | Both | OAuth was cancelled or expired, or `NIMBLE_API_KEY` is unset in the CLI | Desktop: reconnect from the plugin's MCP settings and approve the scopes again. CLI: `echo $NIMBLE_API_KEY`, export it, then restart Codex |
| `/skills` lists nothing after `npx skills add` | CLI | The skills landed outside Codex's discovery path | Confirm the files exist under `~/.agents/skills/` and re-run with `-a codex` |
The packaged plugin is built from the [Nimble agent-skills repository](https://github.com/Nimbleway/agent-skills).
[Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code), Snowflake's native AI coding agent for the terminal, installs the Nimble MCP server with a single command. OAuth handles authentication — no API key required.
Adds the Nimble MCP server to Snowflake's terminal agent.
```bash theme={"system"}
cortex mcp add nimble https://mcp.nimbleway.com/mcp --transport http
```
No `Authorization` header is needed. The Nimble MCP server advertises OAuth and Cortex Code runs the flow on first connection, storing tokens in your OS keychain.
Run `cortex mcp list` to confirm, then `cortex mcp start`.
Cortex Code writes the server to `~/.snowflake/cortex/mcp.json`. Once `nimble` shows as connected, its tools are namespaced as `mcp__nimble__search`, `mcp__nimble__extract`, `mcp__nimble__map`, and `mcp__nimble__crawl`.
For organization-wide rollout via managed settings, see the [full Cortex Code integration guide](/integrations/partnerships/snowflake/cortex-code).
Installs the Nimble skills into your project.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills
```
Run `npx skills add Nimbleway/agent-skills --list` to see what was installed.
The skills reach Agents and Extract Templates through the MCP server. After installing via `npx skills`, connect the server manually:
```bash theme={"system"}
claude mcp add --transport http nimble-mcp-server https://mcp.nimbleway.com/mcp \
--header "Authorization: Bearer ${NIMBLE_API_KEY}"
```
## What's Next
End-to-end tutorial: competitive price analysis across Amazon, Walmart, and
Nike, from a single prompt
Prefer a visual interface? Create agents in the browser
Need enterprise scale or managed delivery?
View the full plugin source on GitHub
# Business Research
Source: https://docs.nimbleway.com/integrations/agent-skills/web-search-skills/business-research
360° company research, competitor monitoring, and market discovery
Research companies, monitor competitors, and discover businesses in any market. These skills run parallel web searches, synthesize findings into structured reports, and track changes across runs.
For shared architecture, install instructions, and the Private Local Web Index, see the [Web Search Skills overview](/integrations/agent-skills/web-search-skills/overview).
## Company Deep Dive
Source code for this skill
A sourced 360-degree report on any company covering funding, leadership, product/tech, market position, news, and strategic outlook.
**Example prompts:**
```
"tell me about Stripe"
"due diligence on Figma"
"background on Cloudflare before our partnership call"
```
**How it works:** Confirms the company identity via web search, then runs parallel research across five dimensions (funding, product, leadership, news, market position). Extracts full content from the top 5-8 most informative URLs and delivers a structured report.
**Output includes:**
* Quick assessment (3-5 key takeaways)
* Per-dimension sections with dates and source URLs
* Strategic outlook
* Full source list
**Modes:** Full deep dive (default), quick mode (skips deep extraction), refresh mode (focuses on what changed).
***
## Competitor Intel
Source code for this skill
Monitors competitors via live web searches and produces a prioritized intelligence briefing. Compares against previous findings to highlight only what's new.
**Example prompts:**
```
"what are my competitors doing"
"competitor update"
"battlecard for Stripe"
```
**How it works:** Loads known competitors from the business profile (or helps identify them on first run). Researches each competitor in parallel for news, funding, hiring signals, product launches, and social activity. Validates high-priority signals and compares against prior briefings.
**Output includes:**
* TL;DR with top 3-5 signals across all competitors
* Per-competitor section with dated, sourced signals
* Cross-competitor patterns and recommended actions
**Signal priority:**
| Priority | Examples |
| - | - |
| **P1 (High)** | M\&A, leadership changes, major funding (\$10M+) |
| **P2 (Medium)** | Product launches, partnerships, major hires |
| **P3 (Low)** | Blog posts, minor hires, opinion pieces |
***
## Market Finder
Source code for this skill
Discovers all businesses of a given type in any geography. Two modes: **Discovery** finds businesses from scratch, **Audit** compares an existing list against fresh discovery to find gaps.
**Example prompts:**
```
"find all dentists in Austin, TX"
"build a list of SaaS companies in the CRM space"
"audit my list of clinics against what's actually out there"
```
**How it works:** Auto-detects the business vertical and loads optimized search strategies. For large areas, tiles the geography into metro areas. Runs parallel searches across Google Maps, Yelp, BBB, and vertical-specific directories. Deduplicates with three-tier matching (place ID, domain, fuzzy name + city).
**Output includes:**
* Ranked business list with name, address, phone, website, rating, review count, and strength score
* Source links per business
* In Audit mode: matched, discovered-only, and reference-only categories with gap analysis
**Vertical presets:** Healthcare, SaaS/Software, Restaurants, Legal/Financial, Auto/Home Services, and a general fallback.
# Marketing
Source: https://docs.nimbleway.com/integrations/agent-skills/web-search-skills/marketing
Track competitor positioning, messaging shifts, pricing changes, and content gaps
Analyze how competitors position themselves online. These skills scrape competitor websites, extract messaging and value props, and track positioning shifts over time.
For shared architecture, install instructions, and the Private Local Web Index, see the [Web Search Skills overview](/integrations/agent-skills/web-search-skills/overview).
## Competitor Positioning
Source code for this skill
Tracks how competitors position themselves online. Scrapes homepages, features pages, pricing pages, and blogs to extract messaging, value props, CTAs, and pricing models. Compares against previous snapshots to surface positioning shifts with before/after tracking.
**Example prompts:**
```
"competitor positioning analysis"
"messaging comparison across competitors"
"content gap analysis"
"battlecard for Stripe"
```
**How it works:** Loads the competitor list from the business profile (limits to 4 with API credit estimates). Captures your own positioning as a baseline, then researches competitors in parallel -- extracting homepage/features/pricing, analyzing blog content, and reviewing social proof. Compares against prior snapshots.
**Output includes:**
### Full Snapshot (first run or 14+ days since last)
* TL;DR for marketing (3-5 actionable insights)
* Messaging matrix (tagline, primary CTA, value props, target audience, pricing model) with verbatim quotes
* Per-competitor positioning profile
* Content gap analysis and positioning white space
* Recommended actions
### Delta Mode (under 14 days since last run)
* Before/after per competitor (tagline shifts, new features, pricing changes, blog themes)
* Marketing implications
### Battlecard Mode
Triggered by `"battlecard [competitor]"`. Produces a structured document with competitor overview, key claims with source URLs, counter-positioning, feature comparison, objection handling, and talking points.
# Overview
Source: https://docs.nimbleway.com/integrations/agent-skills/web-search-skills/overview
One-command web intelligence workflows for any AI agent
One-command workflows that turn any AI agent into a web intelligence powerhouse. Web Search Skills spawn parallel sub-agents, gather live web data via Nimble APIs, synthesize findings, and deliver structured reports with dates and source URLs.
Compatible with any AI agent or platform that supports the [Agent Skills](https://agentskills.io) protocol -- including [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), and the [Vercel Agent Skills CLI](https://www.npmjs.com/package/skills). Powered by the [Nimble CLI](/nimble-sdk/sdks/cli).
Source code for all skills
## Skills Categories
360° company research, competitor monitoring, and market discovery
Track competitor positioning, messaging shifts, pricing changes, and content
gaps
Meeting prep with attendee research, and local business discovery
Live SEO intelligence from keyword research to AI search visibility
### Individual Skills
Web Search Skills also extend into vertical-specific workflows:
| Category | Skill | Description |
| - | - | - |
| [Business Research](/integrations/agent-skills/web-search-skills/business-research) | [Company Deep Dive](https://github.com/Nimbleway/agent-skills/tree/main/skills/company-deep-dive) | Sourced 360° report on any company |
| | [Competitor Intel](https://github.com/Nimbleway/agent-skills/tree/main/skills/competitor-intel) | Monitor competitor news, funding, hiring, and product launches |
| | [Market Finder](https://github.com/Nimbleway/agent-skills/tree/main/skills/market-finder) | Discover companies in any market or segment |
| [Marketing](/integrations/agent-skills/web-search-skills/marketing) | [Competitor Positioning](https://github.com/Nimbleway/agent-skills/tree/main/skills/competitor-positioning) | Track messaging shifts, pricing changes, and content gaps |
| [Productivity](/integrations/agent-skills/web-search-skills/productivity) | [Meeting Prep](https://github.com/Nimbleway/agent-skills/tree/main/skills/meeting-prep) | Research attendees and their companies before any meeting |
| | [Local Places](https://github.com/Nimbleway/agent-skills/tree/main/skills/local-places) | Discover local businesses in any neighborhood |
| [SEO](/integrations/agent-skills/web-search-skills/seo) | [SEO Intel](https://github.com/Nimbleway/agent-skills/tree/main/skills/seo-intel) | Live SEO intelligence across keyword research, rank tracking, site audits, content gaps, competitor keywords, AI visibility, and GitHub SEO |
## How It Works
Under the hood, Web Search Skills intelligently orchestrate Nimble's [Web Tools](/integrations/agent-skills/web-tools-skills/nimble-web-expert) -- [Search](/nimble-sdk/web-tools/search), [Extract](/nimble-sdk/web-tools/extract/quickstart), [Map](/nimble-sdk/web-tools/map), and [Crawl](/nimble-sdk/web-tools/crawl) -- to fetch data from the web as needed. They also discover and trigger the best [templates](/nimble-sdk/web-tools/extract/template) for the task at hand, choosing from hundreds of pre-built Extract Templates optimized for specific websites and data types.
For bulk operations like table enrichments, skills automatically create batch background tasks -- processing rows in parallel without blocking the conversation.
Every Web Search Skill follows the same pattern:
Checks the Nimble CLI is installed and loads the business profile.
Understands the task, asks clarifying questions, and aligns on scope before
starting.
Spawns sub-agents that orchestrate Nimble Web Tools for concurrent data
gathering across multiple web sources.
Synthesizes findings and deduplicates against previous runs.
Delivers structured output with verified dates and source URLs.
Detects connected integrations (Notion, Slack, Google Drive, and more) and
proposes where to store the report. Can also generate interactive apps or
trigger follow-up skills.
Skills learn from previous runs and only surface what's new. Run the same skill twice and the second run highlights changes, not repeats.
## Private Local Web Index
Skills maintain a local web knowledge index at `~/.nimble/memory/` -- live web intelligence that compounds across sessions, so your agent never starts from scratch.
* **Local-first indexes** -- per-directory entity catalogs for instant lookup, no vector DB needed
* **[Obsidian](https://obsidian.md/)-compatible** -- `[[wiki links]]` cross-reference people, companies, and competitors. Open `~/.nimble/memory/` in Obsidian to browse the intelligence graph
* **Cross-entity synthesis** -- patterns across competitors, pricing trends, and market signals surface automatically
* **Ad-hoc insights** -- say "remember this" mid-conversation and it compounds into the right entity page
* **Activity log** -- grep-friendly record of what was learned, when, and by which skill
Every finding carries a verified event date and source URL. Stale signals are dropped, not reported.
## Quick Install
Installs the Nimble skills and configures the MCP server.
```bash theme={"system"}
claude plugin marketplace add Nimbleway/agent-skills && \
claude plugin install nimble@nimble-plugin-marketplace
```
Run `/mcp` and confirm `nimble` appears in your connected servers.
One click, then confirm the install in Cursor.
Then replace `NIMBLE_API_KEY` in **Settings → MCP Servers → nimble-mcp-server** with your [Nimble API key](https://online.nimbleway.com/settings/api-keys). The deeplink ships a placeholder, not a key.
Teaches the agent which Nimble product fits a given job.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a cursor
```
Open Settings, then MCP, and confirm `nimble-mcp-server` is listed.
Installs the Nimble skills into your project.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills
```
Run `npx skills add Nimbleway/agent-skills --list` to see what was installed.
## Prerequisites
Requires the [Nimble CLI](/nimble-sdk/sdks/cli) and a [Nimble API key](https://online.nimbleway.com/settings/api-keys). See [Plugin Installation](/integrations/agent-skills/plugin-installation) for full setup instructions.
# Productivity
Source: https://docs.nimbleway.com/integrations/agent-skills/web-search-skills/productivity
Meeting prep with attendee research, and local business discovery
Walk into meetings fully briefed and discover local businesses in any neighborhood. These skills gather live web data and deliver structured, actionable results.
For shared architecture, install instructions, and the Private Local Web Index, see the [Web Search Skills overview](/integrations/agent-skills/web-search-skills/overview).
## Meeting Prep
Source code for this skill
Researches meeting attendees and their companies before any meeting. Surfaces roles, recent activity, company context, talking points, and cross-attendee relationships.
**Example prompts:**
```
"prepare me for my meeting with Sarah Chen from Acme Corp"
"who am I meeting with tomorrow"
"brief me on the attendees for my 2pm meeting"
```
**How it works:** Accepts attendee names and companies directly, or parses them from a calendar invite. Researches each person's role, background, recent activity, and social presence. Gathers company context (news, funding, product launches). Maps cross-attendee connections.
**Output includes:**
* Per-attendee profile (role, background, recent activity, published content)
* Company context per organization
* Talking points based on shared interests and recent activity
* Cross-attendee relationships
* Source URLs
***
## Local Places
Source code for this skill
Discovers, enriches, and scores local businesses in any neighborhood. Returns a structured, ranked list with an optional interactive map.
**Example prompts:**
```
"find all coffee shops in SoHo"
"map every bar in Austin's 6th Street"
"build a neighborhood guide for Williamsburg, Brooklyn"
```
**How it works:** Identifies business type and location, then searches across Google Maps, Yelp, BBB, and vertical-specific directories in parallel. Enriches each business with reviews, ratings, social presence, and contact info. Assigns confidence scores based on source count and review volume.
**Output includes:**
* Ranked business list with name, address, phone, website, rating, review count, and social links
* Confidence scores per business
* Interactive HTML map (optional)
* Source links
# SEO
Source: https://docs.nimbleway.com/integrations/agent-skills/web-search-skills/seo
Live SEO intelligence from keyword research to AI search visibility
Answer any SEO question about a website with live data, not checklist advice. This skill routes natural language SEO queries to the right workflow, gathers evidence from live SERPs and real pages, and delivers structured reports with source URLs.
For shared architecture, install instructions, and the Private Local Web Index, see the [Web Search Skills overview](/integrations/agent-skills/web-search-skills/overview).
## SEO Intel
Source code for this skill
A single SEO skill that covers the full lifecycle from keyword research to AI search visibility. Detects intent from natural language and dispatches to one of seven workflows. Every finding is grounded in live web data, not LLM guesses.
**Example prompts:**
```
"find keyword opportunities for my SaaS product"
"run an SEO audit on example.com"
"check our AI visibility vs competitors"
"track rankings for these 5 keywords"
"what content gaps do we have vs competitor.com"
"audit the SEO on our GitHub repo"
```
**How it works:** Parses the request and classifies it against seven workflow intents. Loads the matching workflow reference, gathers live data via Nimble Search, Extract, Map, Crawl, or dedicated AI platform agents, then synthesizes a structured report with evidence and source URLs. Compares against prior runs in the local memory index to surface only what changed.
**Output includes:**
* TL;DR with the top findings and recommended actions
* Per-workflow sections with dated, sourced evidence
* Confidence tiers on every finding
* Source URLs for every claim
* Delta mode across runs (what changed since last time)
### Workflows
| Workflow | What it does | Example trigger |
| - | - | - |
| Keyword Research | Discovers opportunities via live SERP analysis, evidence-based difficulty scoring, and GEO/AEO surface classification | `"find keyword opportunities for my SaaS product"` |
| Rank Tracker | Tracks live SERP positions with JSON snapshots, smart date windowing, and delta reports across runs | `"track rankings for these 5 keywords"` |
| Site Audit | Full-crawl technical and on-page audit with JavaScript rendering, structured metadata parsing, and confidence-tiered findings | `"run an SEO audit on example.com"` |
| Content Gap | Parallel domain mapping, topic coverage comparison, and tiered SERP validation | `"what content gaps do we have vs competitor.com"` |
| Competitor Keywords | Full-site crawl with body-only anchor extraction, theme clustering, and per-domain delta snapshots | `"reverse-engineer competitor.com's SEO"` |
| AI Visibility | Brand visibility measurement across ChatGPT, Perplexity, Google AI, Gemini, and Grok via dedicated agents | `"check our AI visibility vs competitors"` |
| GitHub SEO | Repository discoverability audit covering metadata, README quality, community health, and AI discoverability | `"audit the SEO on our GitHub repo"` |
### AI Visibility
Measures brand presence where users increasingly start their searches: AI answer engines. Queries five dedicated agents in parallel -- ChatGPT, Perplexity, Google AI Overview, Gemini, and Grok -- then attributes mentions, citations, and source URLs per platform. Applies Princeton's GEO optimization methodology to recommend content changes that improve citation likelihood.
### Workflow Chaining
Workflows feed into each other. The skill suggests natural next steps after every run:
* **Site Audit → Keyword Research → Content Gap → Rank Tracker** (full SEO setup)
* **Keyword Research → Competitor Keywords → Content Gap** (editorial planning)
* **AI Visibility → Content Gap → Keyword Research** (generative search optimization)
Context carries across runs -- discovered domains, keyword lists, and business profile data do not get re-asked.
# Nimble Web Expert
Source: https://docs.nimbleway.com/integrations/agent-skills/web-tools-skills/nimble-web-expert
Turn your AI coding assistant into a web scraping expert
The Nimble Web Expert skill gives your agent the ability to search, extract, scrape, map, crawl, and run pre-built Extract Templates across any website. Fast, incremental, and immediately responsive. This is the only way your AI assistant can access live websites.
Powered by the [Nimble CLI](https://www.npmjs.com/package/@nimble-way/nimble-cli) and built on the open-source [Agent Skills](https://agentskills.io) standard for cross-platform agent compatibility.
Source code for this skill
## Tools Overview
| Tool | Description |
| - | - |
| **Search** | Accurate, real-time web search. 8 focus modes: general, coding, news, academic, shopping, social, geo, location. |
| **Extract** | Scalable data collection with stealth unblocking. Get clean, real-time HTML and structured data from any URL. Supports JS rendering and browser emulation. |
| **Map** | Fast URL discovery and site structure mapping. Plan extraction workflows before running them. |
| **Crawl** | Extract contents from entire websites in a single request. Collect large volumes of web data automatically. |
| **Extract Templates** | Run pre-built Extract Templates for structured data from popular websites (Amazon, Google, LinkedIn, and hundreds more). |
## Quick Install
Installs the Nimble skills and configures the MCP server.
```bash theme={"system"}
claude plugin marketplace add Nimbleway/agent-skills && \
claude plugin install nimble@nimble-plugin-marketplace
```
Run `/mcp` and confirm `nimble` appears in your connected servers.
One click, then confirm the install in Cursor.
Then replace `NIMBLE_API_KEY` in **Settings → MCP Servers → nimble-mcp-server** with your [Nimble API key](https://online.nimbleway.com/settings/api-keys). The deeplink ships a placeholder, not a key.
Teaches the agent which Nimble product fits a given job.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a cursor
```
Open Settings, then MCP, and confirm `nimble-mcp-server` is listed.
Installs the Nimble skills into your project.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills
```
Run `npx skills add Nimbleway/agent-skills --list` to see what was installed.
## Prerequisites
### 1. Nimble CLI
Install the Nimble CLI globally:
```bash theme={"system"}
npm i -g @nimble-way/nimble-cli
```
Verify with:
```bash theme={"system"}
nimble --version
```
### 2. Nimble API Key
Get your key at [online.nimbleway.com](https://online.nimbleway.com/).
Set the `NIMBLE_API_KEY` environment variable using your platform's method:
Add to `~/.claude/settings.json`:
```json theme={"system"}
{
"env": {
"NIMBLE_API_KEY": "your-api-key-here"
}
}
```
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key-here"
```
Or add to your shell profile (`~/.bashrc`, `~/.zshrc`):
```bash theme={"system"}
echo 'export NIMBLE_API_KEY="your-api-key-here"' >> ~/.zshrc
```
* Add skills to `.github/skills/` in your repository
* Configure API key using GitHub Actions secrets in the copilot environment
* Or set as environment variable in your shell
## Quick Start
The Nimble Web Expert skill activates automatically when you ask for live web data:
```
"Search for recent AI developments"
-> nimble search --query "recent AI developments" --search-depth lite
"Extract the content from this URL"
-> nimble extract run --url "https://example.com" --parse --format markdown
"Scrape prices from this product page"
-> nimble extract run --url "https://example.com/product" --render --parse --format markdown
"Map all the pages on this docs site"
-> nimble map --url "https://docs.example.com" --limit 100
"Crawl the API docs section"
-> nimble crawl run --url "https://docs.example.com/api" --limit 50
```
Use this skill for immediate, one-off data needs. For workflows you will run
repeatedly, have it build an [Extract Template](/nimble-sdk/web-tools/extract/template)
or schedule an [Extract Job](/nimble-sdk/agentic/jobs) instead.
## Command Examples
### Search
Accurate, real-time web search with 8 focus modes (general, coding, news, academic, shopping, social, geo, location).
```bash theme={"system"}
# Lite search (default, fast, token-efficient)
nimble search --query "React hooks tutorial" --focus coding --search-depth lite
# Search with AI-generated answer summary
nimble search --query "what is WebAssembly" --include-answer --search-depth lite
# News search with time filter
nimble search --query "AI developments" --focus news --time-range week --search-depth lite
# Domain-filtered search
nimble search --query "auth best practices" \
--include-domain github.com \
--include-domain stackoverflow.com \
--search-depth lite
```
Use `--search-depth lite` (default) for fast responses (1-3s). Use
`--search-depth deep` when you need full page content for archiving or
full-text analysis.
### Extract
Get clean, structured data from any URL with stealth unblocking and JS rendering.
```bash theme={"system"}
# Standard extraction (always use --parse --format markdown)
nimble extract run --url "https://example.com/article" --parse --format markdown
# Render JavaScript for SPAs and dynamic content
nimble extract run --url "https://example.com/app" --render --parse --format markdown
# Extract with geolocation
nimble extract run --url "https://example.com" --country US --city "New York" --parse --format markdown
```
### Map
Discover all URLs on a site to plan extraction workflows.
```bash theme={"system"}
# Map URLs on a site
nimble map --url "https://docs.example.com" --limit 100
# Include subdomains
nimble map --url "https://example.com" --domain-filter subdomains
```
### Crawl
Extract content from entire websites asynchronously.
```bash theme={"system"}
# Start a crawl (always set --limit)
nimble crawl run --url "https://docs.example.com" --limit 50
# Crawl with path filtering
nimble crawl run --url "https://example.com" \
--include-path "/docs" \
--include-path "/api" \
--limit 100
# Check crawl status
nimble crawl status --id "crawl-id"
# List all crawls
nimble crawl list
```
For LLM-friendly output, prefer `map` + `extract --parse --format markdown` on
individual pages. Crawl returns raw HTML which can be very large.
## One-off data vs reusable workflows
This skill covers both, so the choice is which Nimble product it reaches for:
| | **Immediate data** | **Reusable workflow** |
| - | - | - |
| **Reach for** | Search, Extract, Map, Crawl | [Extract Template](/nimble-sdk/web-tools/extract/template), [Extract Jobs](/nimble-sdk/agentic/jobs) |
| **Speed** | Fast and direct | Slower to set up, then repeatable |
| **Output** | Extracted data now | A template or a scheduled job |
| **Use when** | "Get me this data now" | "I'll need this repeatedly" |
For open-ended research with citations rather than extraction, use an [Agent](/nimble-sdk/web-search-agents/overview).
## About Agent Skills
This skill follows the [Agent Skills](https://agentskills.io) open-source standard, making it compatible with multiple AI agent platforms. Install using the [Skills CLI](https://www.npmjs.com/package/skills), the standard package manager for the Agent Skills ecosystem.
## Full Command Reference
For the complete list of options, flags, and advanced usage patterns, see the [SKILL.md on GitHub](https://github.com/Nimbleway/agent-skills/blob/main/skills/nimble-web-expert/SKILL.md).
# Claude AI Connectors
Source: https://docs.nimbleway.com/integrations/connectors/anthropic/claude-connectors
Add Nimble to Claude.ai as a custom connector for real-time web search, extraction, and crawling.
Add Nimble to Claude.ai as a custom connector and Claude can search, extract, map, and crawl the web on its own — no code required.
## Supported Surfaces
Custom connectors work across Anthropic's products:
* **Claude.ai** (web)
* **Claude Desktop** (macOS, Windows)
* **Claude Code**
* **Claude Cowork**
## Prerequisites
* A [Claude.ai](https://claude.ai) account on a plan that supports connectors (Pro, Max, Team, or Enterprise).
* A [Nimble account](https://online.nimbleway.com/signup) — sign in via OAuth during setup. No API key needed.
## Installation
In Claude.ai, navigate to **Customize > Connectors**.
Click the **+** button, then choose **Add custom connector**.
Give the connector a name (e.g. `Nimble MCP`) and set the remote MCP server URL to:
```
https://mcp.nimbleway.com/mcp
```
Click **Add**. The Nimble connector now appears in your connectors list with a `CUSTOM` badge.
Open the new **Nimble MCP** connector and click **Connect**.
Review the permissions Claude is requesting and click **Approve** to authenticate with your Nimble workspace. Once approved, the connector is live inside Claude conversations.
For Anthropic's full reference on custom connectors, see the [Claude support article](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp).
## Tools
Once connected, Claude calls any of the Nimble MCP server's tools automatically, based on the user's prompt.
| Tool | Use Case |
| - | - |
| `nimble_search` | Web search across multiple engines with configurable depth |
| `nimble_extract` | Pull clean content from any URL |
| `nimble_map` | Discover all URLs on a domain |
| `nimble_crawl` | Crawl an entire site with path filtering |
| `nimble_agents_run` | Run a custom Agent |
For the full list of MCP tools and parameters, see the [Nimble MCP Server reference](/integrations/mcp-server/mcp-server).
## Power Your Flow with Nimble Agent Skills
Nimble is an **official Anthropic plugin partner**. The Nimble plugin ships in Claude's **Anthropic & Partners** directory and bundles the MCP server with a curated set of agent skills — opinionated workflows that guide Claude through the most common web data jobs (research, scraping, mapping, and crawling) so you get the best of Nimble's Web Tools without writing a prompt from scratch.
Once installed, just type `/` in any new task to invoke a skill directly:
### Install the Nimble plugin
From either the **Cowork** or **Claude Code** tab in Claude Desktop, click **Customize** in the left sidebar, then click **Browse plugins** to open the Directory.
Switch to the **Plugins** tab, select **Anthropic & Partners**, and search for `Nimble`.
Click the Nimble card to open its details, then click **Install**. The plugin saves locally on your machine and the bundled MCP server, agent skills, and sub-agents register automatically.
Once installed, Nimble's agent skills are available across your conversations. Type `/` in chat to browse them, or let Claude invoke them automatically based on your prompt.
For Anthropic's full reference, see [Use plugins in Claude Cowork](https://support.claude.com/en/articles/13837440-use-plugins-in-claude-cowork).
## How It Works
Type a prompt that needs live web data. Claude detects when the Nimble connector can help.
Claude routes the request to the Nimble MCP server, which runs the search, extraction, or crawl in real time.
Results return to the conversation as structured data Claude can quote, cite, or summarize.
## Example Prompts
> *"Find the top 5 AI agent platforms launched this year and summarize their pricing pages."*
Claude calls `nimble_search` for the list, then `nimble_extract` on each pricing page, then summarizes.
> *"Crawl docs.example.com and list every page that mentions 'rate limits.'"*
Claude calls `nimble_crawl` with a path filter, then surfaces matches with URLs.
> *"Pull the headline, author, and publish date from this article: `https://example.com/article`"*
Claude calls `nimble_extract` and returns the structured fields.
> *"Show me every page on nimbleway.com under /blog."*
Claude calls `nimble_map` and lists the discovered URLs.
## Next Steps
Full MCP server reference — Cursor, Claude Code, and Claude Desktop setup
Use Nimble as a tool inside the Anthropic SDK in Python or Node
Web search with depth levels, filtering, and AI answers
Extract clean content from any URL
# Anthropic SDK
Source: https://docs.nimbleway.com/integrations/connectors/anthropic/sdk
Use the Nimble SDK as a tool inside Claude's tool-use API.
Give Claude models real-time web data by wiring the Nimble SDK into Anthropic tool use. No extra packages needed.
## Prerequisites
```bash Python theme={"system"}
pip install anthropic nimble_python
```
```bash Node theme={"system"}
npm install @anthropic-ai/sdk @nimble-way/nimble-js
```
Set environment variables:
```bash theme={"system"}
export ANTHROPIC_API_KEY="your-anthropic-api-key"
export NIMBLE_API_KEY="your-nimble-api-key"
```
Get a Nimble API key from the [dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available).
The examples set the Nimble client's `client_source` (`clientSource` in Node) to `anthropic-sdk`, which sends an `X-Client-Source` header that attributes requests to this integration. It defaults to `sdk` when unset.
## Quick Start: Tool Runner
The Anthropic Python SDK includes a `tool_runner` that handles the tool-calling loop automatically. Define Nimble tools with the `@beta_tool` decorator and the SDK manages execution, message history, and retries.
```python Python theme={"system"}
import os
import json
import anthropic
from anthropic import beta_tool
from nimble_python import Nimble
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="anthropic-sdk")
@beta_tool
def nimble_search(query: str) -> str:
"""Search the web using Nimble and return relevant results.
Args:
query: The search query to execute.
"""
result = nimble_client.search(query=query)
return json.dumps(result, default=str)
@beta_tool
def nimble_extract(url: str) -> str:
"""Extract clean content from a URL using Nimble.
Args:
url: The URL to extract content from.
"""
result = nimble_client.extract(url=url)
return json.dumps(result, default=str)
runner = client.beta.messages.tool_runner(
model="claude-sonnet-5",
max_tokens=4096,
tools=[nimble_search, nimble_extract],
messages=[{"role": "user", "content": "What are the latest trends in AI agents?"}],
)
for message in runner:
for block in message.content:
if hasattr(block, "text"):
print(block.text)
```
The `@beta_tool` decorator auto-generates the JSON schema from type hints and doc strings, so no manual schema definition is needed.
## Agent API V2: autonomous research tool
The tools above (`search`, `extract`, `crawl`, `map`, and [Extract Templates](/nimble-sdk/web-tools/extract/template)) are **synchronous and single-shot**. One call returns data directly, and Claude does the reasoning.
[Agent API V2](/nimble-sdk/web-search-agents/overview) is different. It exposes an **asynchronous research agent** that plans, searches across many sources, and returns a synthesized answer with a per-claim [trust report](/nimble-sdk/web-search-agents/trust). The lifecycle is **create a run → poll to a terminal state → retrieve the result**. Wrap that whole lifecycle in one function so Claude sees a single "deep research" tool.
### 1. Wrap the run lifecycle
This helper uses a stable `agent_name`, so every call — from this process or any other — reuses the same agent and its memory instead of spinning up a fresh one each time. It creates the run, polls until terminal, handles `failed` and `cancelled` runs, and returns the answer plus its trust and citation metadata. Errors return a safe message, so no API key or raw exception is surfaced.
```python Python theme={"system"}
import os
import time
from nimble_python import Nimble
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="anthropic-sdk")
# Stable name for this integration's research agent. The first call creates
# it; every later call (here or from another process) reuses it and its memory.
# Pick a name unique to this deployment - anyone reusing the same name on the
# same account reuses the same agent and its memory.
NIMBLE_AGENT_NAME = os.environ["NIMBLE_AGENT_NAME"]
def run_deep_research(query: str, timeout_s: int = 300) -> dict:
"""Run one Agent API V2 research task end to end and return a cited answer."""
try:
# Create-or-reuse the named agent, then start the run.
run = nimble_client.agents.run(input=query, agent_name=NIMBLE_AGENT_NAME)
agent_id = run.web_search_agent_id
# Poll until the run reaches a terminal state.
deadline = time.time() + timeout_s
while run.is_active:
if time.time() > deadline:
return {"status": "timeout", "error": "Run did not finish in time."}
time.sleep(10)
run = nimble_client.agents.runs.get(run.id, agent_id=agent_id)
# Handle failed or cancelled runs.
if run.status != "completed":
return {"status": run.status, "error": f"Run ended as '{run.status}'."}
# Retrieve the completed result and summarize its trust report.
result = nimble_client.agents.runs.result(run.id, agent_id=agent_id)
output = result.output
return {
"status": "completed",
"answer": output.content,
"confidence": output.trust.confidence,
"citations": [
{"url": s.url, "title": s.title, "source_type": s.type}
for s in output.trust.sources
],
}
except Exception:
# Never leak credentials or raw exceptions back to the model.
return {"status": "error", "error": "Nimble request failed."}
```
Pass an `output_schema` to `agents.run` (or `agents.runs.create`) to get structured JSON in `result.output` instead of prose. Trust is then keyed by JSON path. See [Dataset Building](/nimble-sdk/web-search-agents/use-cases/dataset-building).
### 2. Expose it with the Tool Runner
Wrap the helper with `@beta_tool` and pass it to `tool_runner`. Claude calls one tool; the async run loop stays hidden.
```python Python theme={"system"}
import os
import json
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
@beta_tool
def nimble_deep_research(query: str) -> str:
"""Run an autonomous, fully-cited web-research task with Nimble Agent API V2.
Use for open-ended questions that need synthesis across multiple sources,
not single-page lookups. Returns a cited answer with a trust grade.
Args:
query: The research question or task, in plain language.
"""
return json.dumps(run_deep_research(query), default=str)
runner = client.beta.messages.tool_runner(
model="claude-sonnet-5",
max_tokens=4096,
tools=[nimble_deep_research],
messages=[{
"role": "user",
"content": "Compare the pricing and positioning of Datadog and Grafana Cloud.",
}],
)
for message in runner:
for block in message.content:
if hasattr(block, "text"):
print(block.text)
```
The tool returns the synthesized answer, an overall `confidence` grade (`high`, `medium`, or `low`), and the source URLs behind it, so Claude can weigh how much to trust each claim. See [Trust](/nimble-sdk/web-search-agents/trust) for the full report structure.
## Messages API (Manual Loop)
For more control, define tools using the Anthropic `input_schema` format and handle the `tool_use` → `tool_result` loop manually.
### 1. Define the Tool Schema
```python Python theme={"system"}
tools = [
{
"name": "nimble_search",
"description": "Search the web using Nimble and return relevant results.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to execute"
}
},
"required": ["query"]
}
},
{
"name": "nimble_extract",
"description": "Extract clean content from a URL using Nimble.",
"input_schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to extract content from"
}
},
"required": ["url"]
}
}
]
```
### 2. Handle the Tool-Use Loop
```python Python theme={"system"}
import os
import json
import anthropic
from nimble_python import Nimble
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="anthropic-sdk")
def process_tool_call(name, tool_input):
if name == "nimble_search":
result = nimble_client.search(query=tool_input["query"])
return json.dumps(result, default=str)
elif name == "nimble_extract":
result = nimble_client.extract(url=tool_input["url"])
return json.dumps(result, default=str)
messages = [
{"role": "user", "content": "What are the latest trends in AI agents?"}
]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Tool-use loop: keep processing until Claude stops calling tools
while response.stop_reason == "tool_use":
tool_use_blocks = [block for block in response.content if block.type == "tool_use"]
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_use_blocks:
result = process_tool_call(tool_use.name, tool_use.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
tools=tools,
messages=messages,
)
for block in response.content:
if hasattr(block, "text"):
print(block.text)
```
### Node.js Example
```typescript Node theme={"system"}
import Anthropic from "@anthropic-ai/sdk";
import Nimble from "@nimble-way/nimble-js";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY, clientSource: "anthropic-sdk" });
const tools: Anthropic.Tool[] = [
{
name: "nimble_search",
description: "Search the web using Nimble and return relevant results.",
input_schema: {
type: "object" as const,
properties: {
query: { type: "string", description: "The search query to execute" },
},
required: ["query"],
},
},
{
name: "nimble_extract",
description: "Extract clean content from a URL using Nimble.",
input_schema: {
type: "object" as const,
properties: {
url: { type: "string", description: "The URL to extract content from" },
},
required: ["url"],
},
},
];
async function processToolCall(name: string, toolInput: Record) {
if (name === "nimble_search") {
return await nimble.search({ query: toolInput.query });
}
if (name === "nimble_extract") {
return await nimble.extract({ url: toolInput.url });
}
}
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "What are the latest trends in AI agents?" },
];
let response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 4096,
tools,
messages,
});
while (response.stop_reason === "tool_use") {
const toolUseBlocks = response.content.filter(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
messages.push({ role: "assistant", content: response.content });
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const toolUse of toolUseBlocks) {
const result = await processToolCall(
toolUse.name,
toolUse.input as Record
);
toolResults.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result),
});
}
messages.push({ role: "user", content: toolResults });
response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 4096,
tools,
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
## How It Works
The Anthropic tool-use flow has three steps:
Pass the tool definitions and user message to Claude. If Claude decides to use a tool, it returns a `tool_use` content block with the tool name and input.
Parse the `tool_use` block and call the corresponding Nimble SDK method. Return the result as a `tool_result` message.
Claude processes the tool result and responds with a text answer. If it needs more data, it may call another tool, and the loop continues until `stop_reason` is `end_turn`.
## Available Tools
Any Nimble SDK method can be exposed as an Anthropic tool. Here are the most common ones:
| Tool | SDK Method | Use Case |
| - | - | - |
| `nimble_search` | `client.search()` | Web search with structured results |
| `nimble_extract` | `client.extract()` | Extract content from a URL |
| `nimble_crawl` | `client.crawl.run()` | Crawl an entire site |
| `nimble_map` | `client.map()` | Discover all URLs on a domain |
See the [Python SDK](/nimble-sdk/sdks/python) and [Node SDK](/nimble-sdk/sdks/node) docs for the full list of methods and parameters.
## Next Steps
Full Python SDK reference with all methods and configuration options
Full Node.js SDK reference with TypeScript support
Agent API V2: autonomous research runs with per-claim trust
Use Nimble with OpenAI function calling and the Agents SDK
# Cloudflare Agents SDK
Source: https://docs.nimbleway.com/integrations/connectors/cloudflare-agents-sdk
Run Nimble Agent research from a Cloudflare Worker. Durable Object-backed start, status, and result tools survive a restart without losing the run.
## Overview
Give a Cloudflare Worker the ability to research the live web. Ask a question in one sentence. Get back a written answer with sources, per-claim citations, and a confidence grade.
The [`@nimble-way/cloudflare-agents`](https://www.npmjs.com/package/@nimble-way/cloudflare-agents) package brings [Nimble Agent](/nimble-sdk/web-search-agents/overview) research into the [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/). Nimble plans the research, searches, and opens pages. It reads JavaScript-heavy sites, cross-checks what it finds, and returns a cited answer. Your Worker sends a task and collects the result.
That research takes minutes, not milliseconds. A `high` effort run takes 5 to 15 minutes, far longer than any Worker request. So the package parks the run in a Durable Object and hands you a key to collect it later.
Without this, you would store the run ID in Durable Object storage, set an alarm to poll it, and build your own create-idempotency so a retry does not bill twice.
* **A cited answer from the live open web**: prose or JSON, with sources, per-claim citations, and confidence grades, read at run time.
* **Gets cheaper the more you run it**: a reused agent remembers which sources worked. Recurring research runs up to 50% cheaper.
* **Start now, collect later**: start in one request, read the result in another. An eviction or a redeploy resumes the same run rather than creating a new one.
* **Never bills you twice**: within one agent instance, the same request can never start a second run.
### Which path to take
Two ways to get Nimble into a Cloudflare agent. Pick one in ten seconds.
The fastest start. No package, no Durable Object binding, no migration. Tools arrive over the wire and the model calls them. Best for interactive agents and short tasks.
For runs that must survive. Run identity lives in durable storage and resumes after a restart or a redeploy. Best when losing a 15-minute research run is unacceptable.
## Prerequisites
A Wrangler version that supports SQLite Durable Objects, and a Workers account with Durable Objects enabled. The agent binds as a SQLite Durable Object, so the project needs a `new_sqlite_classes` migration.
`agents` is a peer dependency, range `>=0.20.0 <1`. Your project supplies it. The package uses `startFiber()` and `onFiberRecovered()`, which land in `0.20.0`.
Required for the build and test toolchain. The published code targets the Workers runtime, not Node.
Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys). Store it with `wrangler secret put`. See [Handling the API key](#handling-the-api-key) for the rules that keep it out of model input.
Pass `nimbleAgentId` to run against a Nimble Agent you already created, for example `wsa_01j9x8...`. Or pass `agent_name` in the request to create one on first use and reuse it by name after that.
Pass one of them for anything that runs more than once. A reused agent keeps its memory, so it gets faster, more accurate, and up to 50% cheaper on recurring work.
Omit both and Nimble provisions a fresh, disposable agent for that run. No memory carries over, so every run starts from zero at full price. Both paths return a Nimble Agent ID you can read back.
Reusing the agent is the Nimble half. For scheduled work, see [Scheduled and recurring runs](#scheduled-and-recurring-runs) for the Cloudflare half.
## Quick Start
```bash theme={"system"}
npm install @nimble-way/cloudflare-agents@0.1.0 agents
```
`0.1.0` is the current release.
```bash theme={"system"}
wrangler secret put NIMBLE_API_KEY
```
Never put the key in `wrangler.jsonc`, in a `vars` block, or in a committed `.dev.vars`.
```jsonc wrangler.jsonc theme={"system"}
{
"name": "my-research-worker",
"main": "src/index.ts",
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{ "name": "NIMBLE_RUN_AGENT", "class_name": "NimbleRunAgent" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["NimbleRunAgent"] }
]
}
```
The binding name must be `NIMBLE_RUN_AGENT`. The package reads it from `env`.
`nodejs_compat` is required, not optional. The Agents SDK imports `node:async_hooks` for the `AsyncLocalStorage` behind every fiber. Without the flag `wrangler deploy` fails to resolve built-in Node modules.
```ts src/index.ts theme={"system"}
import { getAgentByName } from "agents";
import type { Env as NimbleEnv } from "@nimble-way/cloudflare-agents";
export { NimbleRunAgent } from "@nimble-way/cloudflare-agents";
interface Env extends NimbleEnv {}
export default {
async fetch(request: Request, env: Env): Promise {
const url = new URL(request.url);
// One instance per trusted principal. See the warning below.
const runAgent = await getAgentByName(env.NIMBLE_RUN_AGENT, "research-session");
if (url.pathname === "/start") {
const outcome = await runAgent.startRun({
request: {
input: "Summarize how the EU AI Act is being enforced in 2026, with sources.",
effort: "high",
use_case: "research",
},
});
return Response.json(outcome);
}
const fiberKey = url.searchParams.get("key");
if (!fiberKey) {
return new Response("Missing key", { status: 400 });
}
if (url.pathname === "/status") {
return Response.json(await runAgent.getRunLifecycleStatus(fiberKey));
}
if (url.pathname === "/result") {
return Response.json(await runAgent.getRunTypedResult(fiberKey));
}
return new Response("Not found", { status: 404 });
},
};
```
This example has no authentication. `/start` bills a Nimble run per request. Add your own auth check before `wrangler deploy`, and give each authenticated principal its own agent instance name instead of the fixed `"research-session"`. See [Handling the API key](#handling-the-api-key).
`startRun` accepts the work and returns before the create is attempted, so a missing `NIMBLE_API_KEY` still returns a successful-looking receipt. The failure appears on the next status read.
Add `@cloudflare/workers-types` to your tsconfig `types`.
```bash theme={"system"}
wrangler dev
curl -s http://localhost:8787/start
```
`/start` returns a queued receipt with a `fiberKey`. Read `/status?key=...` a few times
to watch the run progress, then `/result?key=...` once the status is terminal.
Stay local until you have added an auth check. `wrangler dev` exercises the Durable
Object, the ledger, and all three tools without exposing a billable endpoint.
```bash theme={"system"}
wrangler deploy
```
Deploy once `/start` is behind your own authentication. See the warning in step 4.
### Example response
`startRun` accepts the work and returns identity, not an answer:
```json theme={"system"}
{
"fiberKey": "nimble-run:generated:b0f1c7d4e2a98f6315c0d7b8a4e6f2913d5c8a70e1b4f9c2a6d3e8b1f704c295",
"accepted": true,
"runId": null,
"status": "queued"
}
```
`runId` is `null` on that first response because creation happens in the background. The Cloudflare Agents SDK calls that background task a fiber. It survives a restart.
`accepted` is `true` when this call started a new run, and `false` when an identical request had already been submitted to this agent instance. On `false` the other fields describe the run that already exists. Check it before treating a response as a new run.
Read the status to get the durable identifiers:
```bash theme={"system"}
curl -s "https://my-research-worker.example.workers.dev/status?key=nimble-run:generated:b0f1c7d4..."
```
```json theme={"system"}
{
"fiberKey": "nimble-run:generated:b0f1c7d4e2a98f6315c0d7b8a4e6f2913d5c8a70e1b4f9c2a6d3e8b1f704c295",
"runId": "task_run_01k5m9v3x8zqhpc2n7t4wr6bde",
"agentId": "wsa_01k5m9v3wq2fhtn8b4r7cxz1sd",
"status": "running",
"pollAttempts": 7,
"recoveredCount": 0,
"lastError": null
}
```
Once the run is terminal, read the result:
```bash theme={"system"}
curl -s "https://my-research-worker.example.workers.dev/result?key=nimble-run:generated:b0f1c7d4..."
```
```json theme={"system"}
{
"fiberKey": "nimble-run:generated:b0f1c7d4e2a98f6315c0d7b8a4e6f2913d5c8a70e1b4f9c2a6d3e8b1f704c295",
"runId": "task_run_01k5m9v3x8zqhpc2n7t4wr6bde",
"agentId": "wsa_01k5m9v3wq2fhtn8b4r7cxz1sd",
"status": "completed",
"result": {
"outputType": "text",
"content": "National market surveillance authorities began issuing formal notices in 2026 [1] ...",
"trust": {
"confidence": "high",
"reasoning": "Primary sources include the official EU register and two national authority notices.",
"claims": [
{
"confidence": "high",
"reasoning": "Stated directly in the enforcement notice.",
"callout": 1,
"citations": [
{
"url": "https://standards-authority.example.com/notices/2026-04",
"title": "Market surveillance notice, April 2026",
"excerpts": ["Enforcement powers apply from ..."]
}
]
}
],
"sources": [
{
"type": "primary",
"url": "https://standards-authority.example.com/notices/2026-04",
"title": "Market surveillance notice, April 2026"
}
]
},
"error": null
},
"error": null
}
```
## How it works
`getAgentByName(env.NIMBLE_RUN_AGENT, name)` returns a stub for one Durable Object. Every run started through that stub is recorded in that object's storage.
The package hashes the full create request with SHA-256: the target agent ID plus `input`, `agent_name`, `effort`, `enable_events`, `input_data`, `output_schema`, `previous_interaction_id`, `skill`, `sources`, and `use_case`. The `fiberKey` is `nimble-run:`, then the agent ID or `generated`, then that hash. It is also the `startFiber()` idempotency key. Calling `startRun` twice with an identical request joins the existing run rather than billing a second one. Change `effort` and you get a different key, because Nimble bills that as different work.
The create call reaches Nimble through `@nimble-way/nimble-js` configured with `maxRetries: 0`. The run ID and Nimble Agent ID are written to the durable ledger before polling starts.
The fiber polls `GET /v2/agents/{agent_id}/runs/{run_id}` every 10 seconds by default. Safe reads retry with bounded exponential backoff and honor `Retry-After`.
Say the Durable Object is evicted mid-run, or the Worker is redeployed. Recovery runs off a persisted alarm rather than waiting for an inbound request, and resumes polling from the stored run ID. It never re-issues a create.
That alarm exists because a fiber is in flight. Once the fiber settles, including at the [polling deadline](#the-polling-deadline-is-local-not-terminal), nothing is left to recover and you read the run yourself.
### Run identity
Three identifiers travel with a run. They are not interchangeable:
| Identifier | Shape | What it is |
| - | - | - |
| `agentId` | `wsa_...` | The durable Nimble Agent. Reused across runs when you pass `nimbleAgentId`. |
| `runId` | `task_run_...` | One research run against that agent. |
| `fiberKey` | `nimble-run::<64 hex>` | The package's local handle for the run. Pass it to `status` and `result`. |
Keep the `fiberKey` client-side or in your own store. It is the only argument the status and result tools need.
## The three tools
Starts a research run and returns immediately.
**Input** (`StartRunInput`):
* `request` (required, `CreateRunRequest`): `input` is the task text and is required. Optional fields are `agent_name`, `effort`, `input_data`, `output_schema`, `previous_interaction_id`, `skill`, `sources`, `use_case`, and `enable_events`.
* `nimbleAgentId` (optional): a `wsa_...` ID. Omit it and Nimble provisions the agent for the run.
* `apiKeyOverride` (optional): an ephemeral per-request key that takes precedence over `env.NIMBLE_API_KEY`. It is never persisted.
**Output** (`StartRunOutcome`): `fiberKey`, `accepted`, `runId`, `status`. `runId` is `null` until the background create lands. `accepted` is `false` when an identical request already exists on this instance.
`use_case` is locked once the agent exists. A later run against the same agent must omit it or repeat its current value, or Nimble returns `422`. `skill`, `sources`, and `output_schema` stay overridable per run. `agent_name` applies only when `nimbleAgentId` is omitted, and `sources` is a `SourceGuidance` object, not a list of domains.
**How create failures surface.** `startFiber` accepts work and returns without waiting for it, so `startRun` resolves before the create is attempted and none of these reach the caller as exceptions. Each one lands as `status: "failed"` with its message in `lastError` on the next `getRunLifecycleStatus`. Status is the error channel.
* `GatedFeatureError` when `effort` is `max`. `max` is a custom-budget tier that is [coming soon](/nimble-sdk/web-search-agents/efforts#max). The package never silently substitutes `x-high`, so ask for `x-high` explicitly if you want it now.
* `CreateRateLimitedError` on a definite `429`. Carries `retryAfterSeconds`. Never retried.
* `AmbiguousCreateError` on a transport failure or a `5xx`, where the write may or may not have landed. Read [Duplicate protection](#duplicate-protection) before you do anything with this.
* `NimbleAgentAPIError` for every other API failure. Carries `statusCode` and `refId`.
Reads the durable ledger for one run.
**Output** (`StatusOutcome`): `fiberKey`, `runId`, `agentId`, `status`, `pollAttempts`, `recoveredCount`, `lastError`.
`status` is one of `queued`, `running`, `completed`, `failed`, or `cancelled`. Two more values come from the package itself: `unknown` when no ledger row exists, and `recovery-blocked` when a run created with `apiKeyOverride` lost that key across a restart. Anything else fails closed rather than being guessed at.
A `recovery-blocked` row never resolves on its own. Background reconciliation skips it deliberately. Only `resumeWithOverride` clears it.
This is a durable read, not a live call, so it is cheap. If the row looks stale, the package makes one safe `GET` to bring it current. It never issues a create. `apiKeyOverride` is only needed to reconcile a row that was started with an override key.
Fetches the Nimble result once the run is terminal.
**Output** (`ResultOutcome`): `fiberKey`, `runId`, `agentId`, `status`, `result`, `error`.
`result` is `null` while the run is still active. Poll `getRunLifecycleStatus` until `status` is terminal, then call this. See [The result surface](#the-result-surface) for what `result` contains.
If the run was started with `apiKeyOverride`, re-supply the same key here. The server fallback key is never substituted, and the result reads as `null` without it.
Two recovery methods sit alongside the three tools. Neither is part of the model-facing surface:
* `reconcileRunLifecycleStatus(fiberKey, apiKeyOverride?)` forces one safe `GET` regardless of the poll throttle, then returns the ledger. Use it for operator repair of a stuck row.
* `resumeWithOverride(fiberKey, apiKeyOverride)` clears a `recovery-blocked` row once the caller re-supplies the key. It resumes an existing run and can never create one.
## The result surface
`getRunTypedResult` normalizes the Nimble envelope into `NimbleResult` without flattening it:
| Field | Type | Meaning |
| - | - | - |
| `outputType` | `"text"` \| `"json"` \| `"unknown"` | Which shape `content` holds. |
| `content` | `string` \| object \| array \| `null` | Prose for text output, structured data for JSON output. `null` on a failed run. |
| `trust` | `NimbleTrust` \| `null` | Confidence, reasoning, per-claim citations, and sources. `null` on a failed run. |
| `error` | `{ message, refId? }` \| `null` | Structured failure detail. `null` on a completed run. |
`trust.claims[]` carries a `confidence` grade, `reasoning`, and `citations[]` with `url`, `title`, and `excerpts`. Text output marks each claim with a `callout` number matching a marker in the prose. JSON output uses a `path` into the structure instead. `trust.sources[]` labels each source `primary` or `secondary`.
Run identity travels with the result. `ResultOutcome` carries `runId` and `agentId` alongside `status` and `error`. A stored result stays traceable back to the run that produced it. See [Trust and citations](/nimble-sdk/web-search-agents/trust) for how the grades are assigned.
## Nimble research vs. Cloudflare AI Search
Both return grounded answers. They ground them in different things.
| Dimension | Nimble Agent | Cloudflare AI Search |
| - | - | - |
| Source | The live open web | Content you indexed into a Cloudflare data source |
| Freshness | Read at run time | As fresh as your last index run |
| Coverage | Anything publicly reachable | Only what you put in |
| Shape | Multi-step research across many pages | Retrieval in a single request |
| Latency | Seconds to minutes, by effort | Sub-second |
| Output | Answer with per-claim trust grades and citations | Retrieved chunks, optionally generated over |
When the answer is already in your corpus, AI Search is the better tool: faster, cheaper, and already wired into your account.
Reach for a Nimble run when the answer sits on the public web and nobody indexed it for you. Competitor pricing this morning. A regulatory change last week. A company profile assembled from twenty sources.
They compose. Index your own documents in AI Search, and commission a Nimble run when the corpus comes up short.
## Cost and latency
Nimble bills per run, at a flat price per [effort level](/nimble-sdk/web-search-agents/efforts). Compute, retrieval, and storage are included.
| Effort | Typical time | Price |
| - | - | - |
| `low` | 10 to 30 seconds | \$0.025 / run |
| `medium` | 1 to 3 minutes | \$0.10 / run |
| `high` | 5 to 15 minutes | \$0.50 / run |
| `x-high` | 15 to 30 minutes | \$2.00 / run |
`high` is the default for a run with no agent identity. A persistent Nimble Agent uses its own stored default, which templates set as high as `x-high` (\$2.00/run). Set `effort` explicitly if cost matters. `max` is a custom-budget tier and is coming soon; requesting it raises `GatedFeatureError`.
Cloudflare charges separately for Durable Object requests, duration, and storage. A run parked between polls costs very little, but the ledger row persists until you remove it. See [Cleanup](#cleanup).
## Running this in production
Everything below is what to get right once the example above works.
### Handling the API key
`NIMBLE_API_KEY` is a Worker secret. It must never appear in:
* model-facing tool schemas or tool inputs
* browser or client state
* run outputs, `lastError` strings, or fixtures
* Worker logs, Gateway logs, screenshots, or evidence
* code examples, including the ones in your own docs
The tool inputs above carry a task and safe overrides. They carry no credential unless your application supplies `apiKeyOverride` server-side. That value is deliberately excluded from the durable ledger and from the fiber recovery snapshot.
If a Worker serves more than one authenticated principal, route each trusted principal to a **separately named** `NimbleRunAgent` instance. Deduplication and the run ledger are scoped to one Durable Object instance. Multiplexing untrusted users into one instance lets them observe and join each other's runs. HTTP authentication is the consuming Worker's job.
### The asynchronous contract
Creation returns identity, not an answer. Everything after that is a read.
| Setting | Value |
| - | - |
| Default status poll interval | 10 seconds (`DEFAULT_POLL_INTERVAL_MS`) |
| Minimum production poll interval | 10 seconds; shorter values are rejected |
| Safe-read retry policy | 3 attempts, 500 ms base delay, 8 second cap (`DEFAULT_RETRY_POLICY`) |
| `429 Retry-After` on a safe read | Honored, capped at the 8 second maximum delay |
| `429` on a create | Fails immediately as `CreateRateLimitedError`, never retried |
| Background polling budget | 5 minutes per fiber |
Override the interval with the `NIMBLE_POLL_INTERVAL_MS` environment variable. Values below 10 seconds are rejected in production. Point the base URL elsewhere with `NIMBLE_BASE_URL`; it defaults to `https://sdk.nimbleway.com`.
### The polling deadline is local, not terminal
Background polling stops after 5 minutes. A `high` run takes 5 to 15 minutes. An `x-high` run takes 15 to 30. A long run routinely outlives that budget. This is expected, and it is not a failure.
When the deadline hits, the package records `"Polling deadline exceeded"` in `lastError` and **leaves `status` at the last real provider status**. It does not mark the run `failed`. The run is still going at Nimble.
After the deadline no background poll is running. Poll `getRunLifecycleStatus` yourself. Each call at least one poll interval after the last update issues one safe `GET` and brings the row current. `reconcileRunLifecycleStatus` forces that read immediately.
Read the deadline as "this Worker stopped watching", not "the run died". On a non-terminal row, `lastError: "Polling deadline exceeded"` means read status again later. It is not a reason to start a second run.
### Duplicate protection
A Nimble run is billable, and running the same request twice bills twice. The package refuses to create one twice by accident:
* **No automatic retry of creation.** Not on `429`, not on `5xx`, not on a transport failure. The underlying client runs with `maxRetries: 0`.
* **Durable deduplication.** The `fiberKey` hash covers the target agent ID and the full create request. It is registered as the Agents SDK idempotency key, so an identical `startRun` joins the existing run and returns `accepted: false`.
* **Ambiguous creates are never resubmitted.** A transport failure or a `5xx` gives no signal about whether the write landed. The package raises `AmbiguousCreateError` and stops.
On `AmbiguousCreateError`, do not resubmit. Reconcile first. List the runs for the agent, or `GET` the run if an ID is already known. Only create a new run once you have established that none exists. Blind resubmission bills a second run and produces two answers to one question.
Recovery follows the same rule. Say the Durable Object is evicted between the create and the confirmation. `onFiberRecovered()` marks the row rather than retrying. `lastError` then reads `Recovered before create was confirmed; not retried (ambiguous write)`.
### Scheduled and recurring runs
The Agents SDK keeps fiber idempotency keys permanently, with no expiry and no automatic pruning. So a scheduled Worker that sends the same prompt to the same instance joins the first run every time. It returns the original answer, with no error and no second charge, and every status field reads healthy.
Give each scheduled run its own Durable Object instance name. This is required for recurring work, not a tidiness measure.
Keep passing the same `nimbleAgentId`. Agent memory and the recurring-cost reduction live on the Nimble side and are unaffected. Rotate only the Cloudflare-side instance name:
```ts theme={"system"}
const today = new Date().toISOString().slice(0, 10);
const runAgent = await getAgentByName(env.NIMBLE_RUN_AGENT, `monitor-${today}`);
```
The dedupe ledger is scoped per instance, so a fresh instance means a fresh run. Rotating also bounds ledger growth and pairs with `destroy()` once the result is stored.
To keep one long-lived instance, you must prune the ledger yourself with `deleteFibers({ status: ["completed", "error", "aborted"], settledBefore })`. Nothing prunes it for you.
### Cleanup
The run ledger lives in the Durable Object's SQLite storage and does not expire on its own. Two habits keep it bounded:
* **Scope instance names.** Name the agent per session, per user, or per job rather than using one global instance forever. The ledger grows with the runs recorded in it.
* **Destroy finished instances.** `Agent` exposes `destroy()`, inherited by `NimbleRunAgent`. Call it once every run in that instance is terminal and the results are stored somewhere durable. Treat it as fire-and-forget.
* **Clear settled dedupe rows.** `deleteFibers({ status, settledBefore, limit })` removes terminal rows only. It defaults to 100 per call and caps at 500, so a busy instance needs repeated calls. Nothing sweeps them automatically.
Do not destroy an instance with a non-terminal run in it. The run continues at Nimble and is billed either way. The local handle is gone, so the result has to be reconciled by run ID.
### Attribution
Every request the package sends to Nimble carries `X-Client-Source: cloudflare-agents`. The package sets it. Do not set it yourself, and do not expect to see it in your own code.
## Optional: routing through AI Gateway
[Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/) can sit in front of Nimble as a Custom Provider. It gives you Gateway analytics and logging over the same traffic.
This is an optional path, not the recommended one. Agent API V2 is a multi-endpoint asynchronous lifecycle, not a chat-completions provider. The Gateway is a transparent HTTP hop, not a lifecycle-aware provider. It understands nothing about runs. The direct route is the default for good reason.
Set the Custom Provider's own `base_url` to `https://sdk.nimbleway.com`, then point the package at the gateway:
```bash theme={"system"}
NIMBLE_BASE_URL=https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/custom-{slug}
```
Everything after `custom-{slug}/` is appended, so `/v2/agents/...` routes correctly.
**Set Retry requests to zero attempts.** A gateway-level retry of the create POST bills a second run, and the package's own `maxRetries: 0` cannot stop it. This is the setting that costs money if you get it wrong.
**Leave Cache Responses off.** It is off by default. A cached status response reports a stale lifecycle state, and a cached create response hides whether a run was actually created.
**Use an unauthenticated gateway.** The package sends a fixed header set and exposes no hook to add one, so `cf-aig-authorization` can never be sent. An authenticated gateway rejects every request.
These are gateway-side settings, configured in the Cloudflare dashboard rather than in your Worker. Decide explicitly whether Gateway logs retain request and response bodies before you route research traffic through it.
## Using the Nimble MCP Server instead
If you want Nimble tools in a Cloudflare agent without adding a dependency, connect the [Nimble MCP Server](/integrations/mcp-server/mcp-server) over Streamable HTTP. The Agents SDK connects to it directly:
```ts theme={"system"}
import { Agent } from "agents";
interface Env {
NIMBLE_API_KEY: string;
}
export class ResearchAgent extends Agent {
async onStart() {
await this.addMcpServer("nimble", "https://mcp.nimbleway.com/mcp", {
transport: {
type: "streamable-http",
headers: {
Authorization: `Bearer ${this.env.NIMBLE_API_KEY}`,
},
},
});
}
}
```
Pass the credential in the `Authorization` header, never in the URL. A key in a URL leaks into server access logs, browser history, and `Referer` headers sent to third parties. Header values are not logged the same way.
This is the shortest path to Nimble tools in a Worker. It trades durability for setup speed: nothing here survives an eviction mid-run.
The MCP server page covers the full transport options, the authentication options including OAuth, and the complete tool table. This section does not repeat them.
[Code Mode](https://developers.cloudflare.com/agents/api-reference/code-mode/) is a third option: the model writes TypeScript against your tools instead of calling them one at a time. It works with either path above and is not documented here.
## Next steps
What a research run is, and how to shape one.
Effort levels, run times, and prices.
How confidence grades and per-claim citations are produced.
Transports, authentication, and the full tool table.
Release notes and the published surface.
Durable execution, fibers, and the Agents SDK.
# Google ADK
Source: https://docs.nimbleway.com/integrations/connectors/google-adk
Connect Google ADK agents to the Nimble web data platform via the MCP server.
Give Google ADK agents real-time web data by connecting to the Nimble MCP server. No extra SDK wiring needed.
[Google ADK (Agent Development Kit)](https://google.github.io/adk-docs/) uses the [Model Context Protocol](https://modelcontextprotocol.io/) to connect agents to external tools. Since Nimble provides a hosted MCP server, ADK agents can discover and use all Nimble tools automatically.
## Prerequisites
```bash theme={"system"}
pip install google-adk
```
Set environment variables:
```bash theme={"system"}
export GOOGLE_API_KEY="your-google-api-key"
export NIMBLE_API_KEY="your-nimble-api-key"
```
Get a Nimble API key from the [dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available).
The MCP examples pass an `X-Client-Source: google-adk` header, and the SDK example sets the Nimble client's `client_source` to `google-adk`, so requests are attributed to this integration.
## Quick Start
Connect an ADK agent to the Nimble MCP server using `McpToolset` with `StreamableHTTPConnectionParams`. ADK auto-discovers all available Nimble tools: search, extraction, mapping, crawling, Extract Templates, and Nimble Agents.
```python Python theme={"system"}
import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
NIMBLE_API_KEY = os.environ["NIMBLE_API_KEY"]
root_agent = LlmAgent(
model="gemini-2.5-flash",
name="web_research_agent",
instruction=(
"You are a research assistant with access to real-time web data. "
"Use the available tools to search the web, extract content from URLs, "
"crawl sites, and discover URLs."
),
tools=[
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://mcp.nimbleway.com/mcp",
headers={"Authorization": f"Bearer {NIMBLE_API_KEY}", "X-Client-Source": "google-adk"}
)
)
],
)
```
### Run the Agent
```python Python theme={"system"}
import asyncio
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
async def main():
session_service = InMemorySessionService()
session = await session_service.create_session(
app_name="nimble_app", user_id="user"
)
runner = Runner(
agent=root_agent,
app_name="nimble_app",
session_service=session_service,
)
from google.genai import types
response = await runner.run_async(
user_id="user",
session_id=session.id,
new_message=types.Content(
role="user",
parts=[types.Part(text="What are the latest trends in AI agents?")]
),
)
for event in response:
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
print(part.text)
asyncio.run(main())
```
## Filter Tools
By default, ADK discovers all Nimble MCP tools. Use `tool_filter` to expose only the tools the agent needs:
```python Python theme={"system"}
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://mcp.nimbleway.com/mcp",
headers={"Authorization": f"Bearer {NIMBLE_API_KEY}", "X-Client-Source": "google-adk"}
),
tool_filter=["nimble_search", "nimble_extract"]
)
```
## Agent API V2: autonomous research tool
The hosted MCP tools above (`nimble_search`, `nimble_extract`, `nimble_map`, `nimble_crawl_run`, and `nimble_extract_templates_run`) are **synchronous and single-shot**. One call returns data directly, and the ADK model does the reasoning.
[Agent API V2](/nimble-sdk/web-search-agents/overview) is different. It exposes an **asynchronous research agent** that plans, searches across many sources, and returns a synthesized answer with a per-claim [trust report](/nimble-sdk/web-search-agents/trust). The lifecycle is **create a run → poll to a terminal state → retrieve the result**.
The Nimble MCP server exposes Agent API V2 too (creating a run, polling its status, and fetching the result), so an MCP-connected ADK agent can call these tools directly. Because a V2 run is asynchronous, you can also wrap the [Nimble SDK](/nimble-sdk/sdks/python) lifecycle in a single Python function. The model then calls one synchronous "deep research" tool, and the create-poll-retrieve loop stays in your code. Register it as an ADK callable tool alongside the MCP toolset shown above.
```bash theme={"system"}
pip install google-adk nimble_python
```
### 1. Wrap the run lifecycle
This helper uses a stable `agent_name`, so every call — from this process or any other — reuses the same agent and its memory instead of spinning up a fresh one each time. It creates the run, polls until terminal, handles `failed` and `cancelled` runs, and returns the answer plus its trust and citation metadata. Errors return a safe message, so no API key or raw exception is surfaced.
```python Python theme={"system"}
import os
import time
from nimble_python import Nimble
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="google-adk")
# Stable name for this integration's research agent. The first call creates
# it; every later call (here or from another process) reuses it and its memory.
# Pick a name unique to this deployment - anyone reusing the same name on the
# same account reuses the same agent and its memory.
NIMBLE_AGENT_NAME = os.environ["NIMBLE_AGENT_NAME"]
def nimble_deep_research(query: str) -> dict:
"""Run an autonomous, fully-cited web-research task with Nimble Agent API V2.
Use for open-ended questions that need synthesis across multiple sources,
not single-page lookups. Returns a cited answer with a trust grade.
Args:
query: The research question or task, in plain language.
Returns:
A dict with the answer, an overall confidence grade, and citations.
"""
try:
# Create-or-reuse the named agent, then start the run.
run = nimble_client.agents.run(input=query, agent_name=NIMBLE_AGENT_NAME)
agent_id = run.web_search_agent_id
# Poll until the run reaches a terminal state.
deadline = time.time() + 300
while run.is_active:
if time.time() > deadline:
return {"status": "timeout", "error": "Run did not finish in time."}
time.sleep(10)
run = nimble_client.agents.runs.get(run.id, agent_id=agent_id)
# Handle failed or cancelled runs.
if run.status != "completed":
return {"status": run.status, "error": f"Run ended as '{run.status}'."}
# Retrieve the completed result and summarize its trust report.
result = nimble_client.agents.runs.result(run.id, agent_id=agent_id)
output = result.output
return {
"status": "completed",
"answer": output.content,
"confidence": output.trust.confidence,
"citations": [
{"url": s.url, "title": s.title, "source_type": s.type}
for s in output.trust.sources
],
}
except Exception:
# Never leak credentials or raw exceptions back to the model.
return {"status": "error", "error": "Nimble request failed."}
```
Pass an `output_schema` to `agents.run` (or `agents.runs.create`) to get structured JSON in `result.output` instead of prose. Trust is then keyed by JSON path. See [Dataset Building](/nimble-sdk/web-search-agents/use-cases/dataset-building).
### 2. Register it as an ADK tool
Pass the function straight into `LlmAgent`. ADK auto-wraps it as a `FunctionTool`, reading the signature and docstring for the schema. The model calls one tool; the async run loop stays hidden.
```python Python theme={"system"}
from google.adk.agents import LlmAgent
research_agent = LlmAgent(
model="gemini-2.5-flash",
name="deep_research_agent",
instruction=(
"You are a research analyst. Use nimble_deep_research for questions "
"that need current, cited web evidence. Report the confidence grade "
"and cite the sources it returns."
),
tools=[nimble_deep_research],
)
```
Run it with the same `Runner` setup shown in the [Quick Start](#run-the-agent). The tool returns the synthesized answer, an overall `confidence` grade (`high`, `medium`, or `low`), and the source URLs behind it, so the model can weigh how much to trust each claim. See [Trust](/nimble-sdk/web-search-agents/trust) for the full report structure.
## Available Tools
ADK auto-discovers these tools from the Nimble MCP server:
| Tool | Description |
| - | - |
| `nimble_search` | Web search with configurable depth and focus modes |
| `nimble_extract` | Extract content from any URL with rendering support |
| `nimble_map` | Discover all URLs on a website via sitemaps and link crawling |
| `nimble_crawl_run` | Crawl multiple pages with path filtering and progress tracking |
| `nimble_extract_templates_run` | Run pre-built [Extract Templates](/nimble-sdk/web-tools/extract/template) for structured data from popular sites |
The tool names above are a curated subset. See the [Nimble MCP Server](/integrations/mcp-server/mcp-server) docs for setup details and the full tool list.
## Next Steps
Full MCP server setup for Cursor, Claude Desktop, and other clients
Agent API V2: autonomous research runs with per-claim trust
Use Nimble with OpenAI function calling and the Agents SDK
Use Nimble with Claude's tool-use API and Tool Runner
# Hermes
Source: https://docs.nimbleway.com/integrations/connectors/hermes
Run durable, cited Nimble Agent API V2 research runs from inside the Hermes agent using the standalone hermes-nimble-agent plugin.
The [`hermes-nimble-agent`](https://pypi.org/project/hermes-nimble-agent/) plugin connects [Hermes](https://hermes-agent.nousresearch.com/) to Nimble [Agent API V2](/nimble-sdk/web-search-agents/overview). It lets a Hermes user start a research run, walk away, and retrieve a cited answer later from any session.
Agent API V2 is not a single search or extract call. It runs a stateful, asynchronous research workflow on Nimble's infrastructure. The plugin returns durable identifiers the moment a run starts, then the research continues after Hermes returns. Any later Hermes session can resume the run using those identifiers.
The standalone plugin is the recommended path. A [production MCP fallback](#production-mcp-fallback) exists for clients that cannot install the plugin, but it does not offer full parity.
## Overview
The plugin adds five tools and one skill to Hermes:
* **Five tools** cover the run lifecycle: discover an agent, create one from a template if needed, start a run, check status, and fetch the result.
* **One skill**, `nimble-agent:agent-research`, teaches Hermes the reuse-first, poll-later workflow so runs are handled correctly without manual prompting.
Every run returns an answer with a trust block: overall confidence, per-claim citations, and verbatim source excerpts. That trust model is the reason to use Agent API V2 instead of a raw search result.
The workflow is asynchronous by design:
1. Start a run. The plugin returns `agent_id` and `run_id` immediately and never blocks.
2. The research continues on Nimble's infrastructure.
3. Check status later, from the same session or a new one, using the two identifiers.
4. Fetch the terminal result once the run completes.
## Prerequisites
The plugin is built and tested against `hermes-agent==0.19.0`. Install it into the **same Python environment** as `hermes-agent`.
Supported on Python 3.11, 3.12, and 3.13. The plugin depends on `nimble-python>=1.0.0,<2`, which pip installs automatically.
Create a Nimble account and generate an API key from [Settings → API Keys](https://online.nimbleway.com/settings/api-keys). A free trial is available. No extra subscription, quota, or availability tier is required beyond a valid key.
## Installation
Install the plugin, then enable it in Hermes.
```bash theme={"system"}
pip install hermes-nimble-agent==0.1.0 # same environment as hermes-agent
hermes plugins enable nimble-agent
```
The plugin slug is `nimble-agent`. Enabling adds it to the Hermes allow-list. Plugins are opt-in and stay disabled until enabled.
Enabling takes effect on the next session. Start a new Hermes session before the tools become visible.
Verify that the plugin and its five tools loaded:
```bash theme={"system"}
hermes plugins list
```
If the tools do not appear, run `HERMES_PLUGINS_DEBUG=1 hermes plugins list` to see discovery details.
## Authentication
The plugin reads your key from the `NIMBLE_API_KEY` environment variable. It is never passed as a tool argument.
```bash theme={"system"}
export NIMBLE_API_KEY="your-nimble-api-key"
```
Use a placeholder or an environment variable. Never commit a real key or paste one into a prompt or config file.
Two behaviors protect the key:
* **Tools stay hidden when the key is missing.** All five tools are gated on `NIMBLE_API_KEY`. If it is absent or empty, Hermes does not show them to the model. Set the key and start a new session to reveal them.
* **The key is scrubbed from errors.** The plugin never stores, logs, or echoes the key, and replaces it with `[REDACTED]` in any error text before returning it.
The plugin also tags every request with the header `X-Client-Source: hermes` automatically. Nothing to configure.
## Available tools
The toolset name is `nimble_agent`. Each tool returns a JSON string and never raises.
| Tool | Purpose | Required inputs | Optional inputs |
| - | - | - | - |
| `nimble_agents_list` | List account agents. Discover an existing agent to reuse. | none | `limit` (1-50), `offset` |
| `nimble_agent_bootstrap` | List prebuilt templates, or create a persistent agent from one. | none | `template`, `display_name` |
| `nimble_agent_run_start` | Start an async run. Returns identifiers immediately. | `agent_id`, `task` | `effort` |
| `nimble_agent_run_status` | Check run status using the identifiers alone. | `agent_id`, `run_id` | none |
| `nimble_agent_run_result` | Fetch the terminal answer and trust metadata. | `agent_id`, `run_id` | none |
How they connect:
* `nimble_agents_list` and `nimble_agent_bootstrap` produce an `agent_id` (`wsa_...`). Reuse an existing agent before creating a new one.
* `nimble_agent_run_start` takes that `agent_id` plus a `task` (the research question) and returns a `run_id` (`task_run_...`).
* `nimble_agent_run_status` and `nimble_agent_run_result` take both identifiers. They work from any session, so the two identifiers are all a later session needs.
### The agent-research skill
The plugin bundles a skill named `nimble-agent:agent-research`. It teaches the full lifecycle: reuse before create, return identifiers within the turn, avoid tight polling, resume from identifiers alone, handle `not_ready` and failed runs, and present answers with citations and confidence.
Plugin skills are opt-in and do not appear in the system-prompt skill index. Load it explicitly when you want Hermes to follow the workflow:
```text theme={"system"}
skill_view('nimble-agent:agent-research')
```
## Complete asynchronous workflow
This example uses placeholder identifiers and a generic research question. Identifier values shown are illustrative.
List existing agents and reuse one when possible:
```text theme={"system"}
nimble_agents_list()
```
If none fits, list templates and create an agent from one:
```text theme={"system"}
nimble_agent_bootstrap() # lists available templates
nimble_agent_bootstrap(template="") # creates an agent from a listed template
```
Use a `template` value from the list the first call returns. Both paths yield an `agent_id` such as `wsa_xxxxxxxxxxxx`.
Start a medium-effort run. `effort` is optional; omit it to use the agent's default.
```text theme={"system"}
nimble_agent_run_start(
agent_id="wsa_xxxxxxxxxxxx",
task="Summarize the main approaches to retrieval-augmented generation and cite sources.",
effort="medium"
)
```
The response returns immediately:
```json theme={"system"}
{
"run": { "run_id": "task_run_xxxxxxxxxxxx", "agent_id": "wsa_xxxxxxxxxxxx", "status": "queued", "effort": "medium", "is_active": true },
"agent_id": "wsa_xxxxxxxxxxxx",
"run_id": "task_run_xxxxxxxxxxxx",
"next": "Save both identifiers. Check status later with nimble_agent_run_status."
}
```
Record `agent_id` and `run_id`. They are the only state needed to resume. You can now end the session or move on to other work.
From the same session or a new one:
```text theme={"system"}
nimble_agent_run_status(agent_id="wsa_xxxxxxxxxxxx", run_id="task_run_xxxxxxxxxxxx")
```
`status` is `queued` or `running` while active, and `completed`, `failed`, or `cancelled` when terminal.
Once the run is `completed`:
```text theme={"system"}
nimble_agent_run_result(agent_id="wsa_xxxxxxxxxxxx", run_id="task_run_xxxxxxxxxxxx")
```
The result carries the answer and its trust block. Present the answer, then list the sources and per-claim citations.
Medium and higher efforts can take several minutes. Return the identifiers to the user rather than holding a tool call open, and space status checks tens of seconds apart.
## Result and trust model
`nimble_agent_run_result` returns the answer wrapped in the plugin's result envelope:
```json theme={"system"}
{
"success": true,
"output": {
"type": "text",
"content": "Retrieval-augmented generation combines...",
"trust": { "confidence": "high", "reasoning": "...", "sources": [ ... ], "claims": [ ... ] }
},
"sources_count": 7,
"claims_count": 7,
"agent_id": "wsa_xxxxxxxxxxxx",
"run_id": "task_run_xxxxxxxxxxxx"
}
```
Envelope specifics:
* **`output.type`** is `text` for a prose answer, or `json` when the agent is configured with an output schema. Structured runs return `content` as an object or array.
* **`sources_count`** and **`claims_count`** are always present integers.
The `trust` block (overall confidence, per-claim citations, primary and secondary source classification, and verbatim excerpts) follows the standard Agent API V2 trust model. See [Trust](/nimble-sdk/web-search-agents/trust) for how grades and citations work, and the [run result schema](/api-reference/public-api/get-agent-run-result) for every field.
The trust block is present but its detail varies with effort. A `low`-effort run may consult few or no live sources, in which case it honestly reports low confidence with zero sources. Use `medium` or higher when citations matter.
## Errors and recovery
Every failure returns a structured envelope: `{ "success": false, "error_type": "...", "error": "...", "retriable": true|false }`. The `agent_id` and `run_id` are preserved even on validation errors, so a run is never lost to a client-side error.
| `error_type` | Meaning | Action | Retriable |
| - | - | - | - |
| `not_ready` | Result requested before the run finished. | Check status, then retry the result later. | Yes |
| `terminal_failure` | Run failed or was cancelled. | Read the server message. Start a new run if appropriate. | No |
| `auth` | API key missing or invalid. | Set a valid `NIMBLE_API_KEY`. | No |
| `permission` | Key lacks access to the resource. | Check account permissions. | No |
| `not_found` | Unknown `agent_id` or `run_id`. | Confirm the identifiers. | No |
| `validation` | Bad or missing arguments. | Fix the arguments and retry. | No |
| `rate_limited` | Too many requests. | Back off, then retry. See [Rate limits](/nimble-sdk/admin/rate-limits). | Yes |
| `timeout` | Request timed out in transit. | Retry. The run may still be active. | Yes |
| `connection` | Network error reaching Nimble. | Retry. The run may still be active. | Yes |
| `dependency` | `nimble-python` is not installed. | Install it in the Hermes environment. | No |
| `protocol` | Unexpected API response shape. | Retry status or result. Report if it persists. | No |
A transport error such as `timeout` or `connection` does not necessarily end the server-side run. The run keeps going on Nimble's infrastructure. Check status again with the same identifiers before starting over.
Poll considerately. Space status checks tens of seconds apart rather than in a tight loop.
## Effort and runtime guidance
`nimble_agent_run_start` accepts an optional `effort` (`low`, `medium`, `high`, `x-high`, `max`). Omit it to use the agent's default. Higher effort reads more sources and takes longer, from seconds at `low` to many minutes at higher tiers.
See [Efforts](/nimble-sdk/web-search-agents/efforts) for the full tier list, typical run times, pricing, and the default. The bundled skill suggests `medium` or higher when citations matter.
Because a run can take minutes, the plugin returns `agent_id` and `run_id` immediately rather than holding a tool call open. Let the model report the identifiers, then check status later and space checks tens of seconds apart.
## Plugin or MCP?
| Consideration | Standalone plugin | Production MCP fallback |
| - | - | - |
| Recommended status | Primary | Secondary fallback |
| Installation | PyPI plus `hermes plugins enable` | `~/.hermes/config.yaml` MCP server entry |
| Run lifecycle | Five dedicated tools (list, bootstrap, start, status, result) | Generic Nimble agent MCP tools |
| Identity requirement | Requires `agent_id` | Requires `agent_id` |
| Resumable runs | Yes, from identifiers alone | Yes, same durable identifiers |
| Trust and citations | Full trust block mapped for you | Raw API result; presentation is up to you |
| Attribution | `X-Client-Source: hermes` sent automatically | Set the header manually in config |
| Tool filtering | Fixed five-tool toolset | Allowlist you define |
| Credential redaction | Built in | Not provided |
| Bundled skill | `nimble-agent:agent-research` included | Not included |
The plugin is the recommended path. Generic MCP connectivity to Nimble does not by itself provide the plugin's lifecycle handling, trust mapping, credential redaction, or bundled skill.
## Production MCP fallback
Use this path only when installing the plugin is not possible. It connects Hermes to the production Nimble MCP server over Streamable HTTP. Treat the block below as a starting configuration and validate it against your Hermes and Nimble MCP versions before relying on it.
Add the server to `~/.hermes/config.yaml`:
```yaml theme={"system"}
mcp_servers:
nimble:
url: "https://mcp.nimbleway.com/mcp"
headers:
Authorization: "Bearer ${NIMBLE_API_KEY}"
X-Client-Source: "hermes"
timeout: 60
tools:
include:
- nimble_agents_list
- nimble_agents_run
- nimble_agents_run_status
- nimble_agents_run_result
```
Notes:
* **Authentication** uses a bearer token. Keep the key in the `NIMBLE_API_KEY` environment variable and reference it with `${NIMBLE_API_KEY}`.
* **Attribution** over MCP is manual. Add `X-Client-Source: hermes` to the headers as shown. It is not sent automatically the way the plugin sends it.
* **The tool allowlist** limits Hermes to the agent run lifecycle. Confirm the exact tool names against your server's advertised tool list, since a name that does not match is silently filtered out, and adjust the list to your needs.
* **Timeouts** should allow for research that runs for minutes. Runs continue server-side even if a request times out.
* Reload the server mid-session with `/reload-mcp`, or restart Hermes.
The MCP fallback requires an explicit `agent_id`, the same as the plugin. It does not provide the plugin's trust mapping, credential redaction, or bundled skill, and is not a full-parity replacement. See the [Nimble MCP Server](/integrations/mcp-server/mcp-server) docs for the full server reference.
## Troubleshooting
| Symptom | Cause | Action |
| - | - | - |
| Plugin does not appear | Not enabled | Run `hermes plugins enable nimble-agent`, then start a new session. |
| Tools are hidden | `NIMBLE_API_KEY` missing | Set the key, then start a new session. |
| Enabled but no tools this session | Enabling applies to the next session | Start a new Hermes session. |
| Wrong Python environment | Plugin not in the `hermes-agent` environment | Reinstall in the same environment. Confirm with `hermes plugins list`. |
| `auth` error | Key missing or invalid | Set a valid `NIMBLE_API_KEY`. |
| Run stays `queued` or `running` | Research still in progress | Wait and check status again. Higher effort takes longer. |
| Lost identifiers | `agent_id` or `run_id` not saved | Use `nimble_agents_list` to find the agent. Preserve identifiers next time. |
| Result says `not_ready` | Run not finished | Check status, then retry the result. |
| `rate_limited` | Too many requests | Back off, then retry. |
| MCP auth or timeout failure | Bad header or short timeout | Check the `Authorization` header and raise `timeout`. |
## Related resources
Install `hermes-nimble-agent` from public PyPI.
Source, README, and release notes.
Learn how Nimble Agent API V2 research runs work.
How confidence, sources, and per-claim citations are produced.
Full reference for the production MCP fallback.
How Hermes discovers and enables plugins.
# LangChain
Source: https://docs.nimbleway.com/integrations/connectors/langchain
LangChain tools and retrievers for web search, content extraction, Extract Templates, Map/Crawl, and resumable Agent API V2 research.
### Overview
The [`langchain-nimble`](https://pypi.org/project/langchain-nimble/) package provides production-grade LangChain integrations for the Nimble web data platform. Built on the official [`nimble_python`](https://pypi.org/project/nimble_python/) SDK, it enables RAG applications and AI agents that can search, extract, scrape with templates, crawl, map, and run resumable Nimble Agent research.
**NimbleToolkit** is the single entry point. Enable the tool families you need with include flags.
**Choose the right capability** (these are not interchangeable):
| Need | Use | Public tool names |
| - | - | - |
| Ranked web results for RAG or agents | Search | `nimble_search` / `NimbleSearchRetriever` |
| Clean markdown/HTML from a known URL | Extract | `nimble_extract` / `NimbleExtractRetriever` |
| Structured scrape of known page types (PDP, jobs, …) | [Extract Templates](/nimble-sdk/web-tools/extract/template) | `nimble_extract_template_*` |
| Multi-page crawl or URL discovery | Crawl / Map | `nimble_crawl`, `nimble_map` |
| Deep research with citations and trust | [Agents API](/nimble-sdk/web-search-agents/overview) | `nimble_web_search_agent_*` |
Do not treat Extract Templates and Agent API V2 as the same product. Legacy `nimble_agent_*` names are **deprecated aliases for Extract Templates only** — they are not repointed to Agent API V2 research agents.
#### Key Features
* **Search depths** — `lite`, `fast`, `deep`, plus focus modes and domain/date filters
* **Extract Templates** — list → get → run structured site scrapers
* **Agent API V2** — separate **start / status / result** tools (resumable; typically 3–15 minutes)
* **Unified toolkit** — one API key; opt into templates, agents, crawl, and map
* **Full async** — sync and async via `nimble_python`
* **Attribution** — every request sends `X-Client-Source: langchain-nimble`
### Requirements
* Python 3.10+
* `langchain-nimble` **4.0.0+** (depends on `nimble_python>=1.2.0,<2.0.0`)
### Quick Start
#### Installation
```bash theme={"system"}
pip install -U langchain "langchain-nimble>=4.0.0" langchain-openai
```
#### Setup
Get your API key from [Nimble's dashboard](https://online.nimbleway.com/account-settings/api-keys) (free trial available):
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
You can also pass `api_key=` into tools, retrievers, or `NimbleToolkit`.
#### Build an AI Agent with the Toolkit
```python theme={"system"}
from langchain.agents import create_agent
from langchain_nimble import NimbleToolkit
from langchain_openai import ChatOpenAI
# Search + Extract on by default; opt into other families as needed
toolkit = NimbleToolkit(
include_crawl=True,
include_map=True,
include_extract_templates=True,
include_web_search_agents=True,
)
tools = toolkit.get_tools()
agent = create_agent(
model=ChatOpenAI(model="gpt-5"),
tools=tools,
system_prompt=(
"You are a research assistant with Nimble web tools.\n"
"Use search/extract for quick lookups, extract templates for "
"structured scrapes, and Agent API V2 (start → status → result) "
"for deep research that may take several minutes."
),
)
response = agent.invoke({
"messages": [(
"user",
"What are the latest developments in AI agents? Summarize key findings.",
)]
})
print(response["messages"][-1].content)
```
### Toolkit Flags
| Flag | Default | Tools |
| - | - | - |
| `include_search` | `True` | `nimble_search` |
| `include_extract` | `True` | `nimble_extract` |
| `include_crawl` | `False` | `nimble_crawl` |
| `include_map` | `False` | `nimble_map` |
| `include_extract_templates` | `False` | `nimble_extract_template_list` / `_get` / `_run` |
| `include_web_search_agents` | `False` | `nimble_web_search_agents_list`, templates list, create, `run_start` / `run_status` / `run_result` |
| `include_agent` | `False` | **Deprecated** — adds `nimble_agent_*` Extract Template aliases and emits `DeprecationWarning`. Prefer `include_extract_templates`. |
You can enable `include_agent` and `include_web_search_agents` together; both families are returned (aliases + V2).
### Extract Templates
For structured scraping of known page types (product pages, listings, and similar), use Extract Templates — not Agent API V2.
```python theme={"system"}
from langchain_nimble import NimbleToolkit
toolkit = NimbleToolkit(
include_search=False,
include_extract=False,
include_extract_templates=True,
)
tools = toolkit.get_tools()
# nimble_extract_template_list → get → run(template=..., params={...})
```
Typical agent workflow:
1. `nimble_extract_template_list` — discover templates for the account
2. `nimble_extract_template_get` — inspect schema / published version
3. `nimble_extract_template_run` — run with `template` + `params`
See the [Extract Templates guide](/nimble-sdk/web-tools/extract/template) and [API reference](/api-reference/extract-templates-api/run-extract-template).
### Agents API
Deep research / enrichment / dataset building via the [Agents API](/nimble-sdk/web-search-agents/overview). Tools are **resumable**: `run_start` returns immediately with identifiers; poll `run_status`, then fetch `run_result`. Do not expect a multi-minute research job to finish inside one tool call.
Agent **`use_case`** (`research`, `enrichment`, or `dataset_building`) is set when the agent is created and locked afterward — that is separate from how you bootstrap a run below.
```python theme={"system"}
from langchain_nimble import NimbleToolkit
toolkit = NimbleToolkit(
include_search=False,
include_extract=False,
include_web_search_agents=True,
)
tools = toolkit.get_tools()
```
| Tool | Role |
| - | - |
| `nimble_web_search_agents_list` | List existing agents (`wsa_…`) |
| `nimble_web_search_agent_templates_list` | List agent templates |
| `nimble_web_search_agent_create` | Create a persistent agent (for `agent_id` runs) |
| `nimble_web_search_agent_run_start` | Start a run (`agent_name`, `agent_id`, or anonymous) |
| `nimble_web_search_agent_run_status` | Poll status |
| `nimble_web_search_agent_run_result` | Fetch output + trust / citations |
#### Bootstrap Options
How you identify the agent on `run_start` (distinct from `use_case`):
| Option | When | How |
| - | - | - |
| **`agent_name` (default for LangChain)** | Stateless tool sessions | `run_start` with `agent_name` + optional `use_case` / `skill` / `sources` — create-or-reuse |
| **`agent_id`** | You persist `wsa_…` | Create (or list) an agent, then `run_start` with `agent_id` |
| **Anonymous** | One-off demos | Omit both `agent_id` and `agent_name` |
Map response fields carefully for status/result tools:
* Start response `id` → **run id** (`task_run_…`)
* Start response `web_search_agent_id` → **agent id** (`wsa_…`)
#### Effort, Use Cases, and Overrides
* **Effort:** `low` | `medium` | `high` | `x-high` | `max`. Plan for **3–15 minutes** on real research (not a few seconds).
* **`use_case`:** `research` (text), `enrichment` (JSON + `input_data`), `dataset_building` (JSON). Locked after agent create — a different value on reuse returns **422**.
* **Run-level overrides** (`skill`, `sources`, `output_schema`) do not mutate the stored agent, except the **first** `agent_name` create with a new name.
* **`input_data`** is enrichment payload only (never stored on the agent); distinct from `output_schema`.
```python theme={"system"}
from langchain_nimble import NimbleAgentRunStartTool, NimbleAgentRunStatusTool, NimbleAgentRunResultTool
start = NimbleAgentRunStartTool()
started = start.invoke({
"input": "Summarize recent Agent API V2 changes for integrators.",
"agent_name": "langchain_docs_research",
"use_case": "research",
"effort": "medium",
"skill": "Focus on integrator-facing API behavior.",
})
agent_id = started["web_search_agent_id"]
run_id = started["id"]
status = NimbleAgentRunStatusTool().invoke({
"agent_id": agent_id,
"run_id": run_id,
})
# Poll until status is completed / failed / cancelled, then:
result = NimbleAgentRunResultTool().invoke({
"agent_id": agent_id,
"run_id": run_id,
})
```
Runnable examples in the package repo: [`examples/agent_api_v2.py`](https://github.com/Nimbleway/langchain-nimble/blob/main/examples/agent_api_v2.py) and [`examples/web_search_agent.py`](https://github.com/Nimbleway/langchain-nimble/blob/main/examples/web_search_agent.py) (templates + search/extract/map/crawl).
### Crawl and Map
```python theme={"system"}
from langchain_nimble import NimbleCrawlTool, NimbleMapTool
crawl_tool = NimbleCrawlTool()
result = crawl_tool.invoke({"url": "https://docs.example.com", "max_pages": 50})
map_tool = NimbleMapTool()
urls = map_tool.invoke({"url": "https://www.example.com"})
```
Crawl polls internally until the job finishes (or times out). Map discovers URLs via sitemap + link crawling.
### Retrievers (RAG)
```python theme={"system"}
from langchain_nimble import NimbleSearchRetriever, NimbleExtractRetriever
search = NimbleSearchRetriever(max_results=5, search_depth="lite")
docs = search.invoke("latest developments in AI")
# Extract: pass the URL as the retriever query
extract = NimbleExtractRetriever(output_format="markdown")
pages = extract.invoke(
"https://docs.langchain.com/oss/python/integrations/providers/overview"
)
```
### Deprecated Aliases (4.0.0)
| Deprecated | Prefer |
| - | - |
| `include_agent=True` | `include_extract_templates=True` |
| `nimble_agent_list` / `_get` / `_run` | `nimble_extract_template_*` |
| `include_agents` | `include_web_search_agents` |
Breaking notes and SDK floor: see the package [CHANGELOG 4.0.0](https://github.com/Nimbleway/langchain-nimble/blob/main/CHANGELOG.md).
### Attribution
The package sets SDK `client_source="langchain-nimble"`, which sends:
```http theme={"system"}
X-Client-Source: langchain-nimble
```
No extra configuration is required.
### Additional Resources
* [GitHub repository](https://github.com/Nimbleway/langchain-nimble)
* [PyPI package](https://pypi.org/project/langchain-nimble/)
* [Agents API overview](/nimble-sdk/web-search-agents/overview)
* [Extract Templates](/nimble-sdk/web-tools/extract/template)
* [Nimble Python SDK](/nimble-sdk/sdks/python)
* [Example cookbook](https://github.com/Nimbleway/cookbook)
* [LangChain integrations overview](https://docs.langchain.com/oss/python/integrations/providers/overview)
# LiteLLM
Source: https://docs.nimbleway.com/integrations/connectors/litellm
Nimble is a native search provider in LiteLLM: reach Nimble Search from the LiteLLM Python SDK or the AI Gateway with a single parameter.
### Overview
[LiteLLM](https://github.com/BerriAI/litellm) is an SDK and AI gateway that gives 100+ LLM providers one OpenAI-shaped interface. Its Search API does the same for web search backends.
Nimble ships as the `nimble` search provider, built into LiteLLM itself. Calls go to Nimble's [Search API](/nimble-sdk/web-tools/search) and come back in LiteLLM's unified search response. No fork, no plugin, no custom adapter.
The provider ships in **LiteLLM v1.98.0 and later**. Pin that floor rather than installing "latest":
```bash theme={"system"}
pip install "litellm>=1.98.0"
```
`v1.97.0` shipped without the provider. An existing install that predates `v1.98.0` needs an upgrade before `search_provider="nimble"` resolves.
### What this integration covers
This is the Search API only. `search_provider: nimble` routes to `POST /v2/search` and nothing else.
Extract, Map, Crawl, and Nimble Agents are not reachable through LiteLLM today. To use those, call the [Nimble SDK](/nimble-sdk/getting-started/overview) directly or connect the [Nimble MCP server](/integrations/mcp-server/mcp-server).
### Quick Start
#### 1. Get a Nimble API key
Get your API key from [Nimble's dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available) and export it:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
#### 2. Search from the Python SDK
Call `search()` with `search_provider="nimble"`:
```python theme={"system"}
import os
from litellm import search
os.environ["NIMBLE_API_KEY"] = "your-api-key"
response = search(
query="latest AI developments",
search_provider="nimble",
max_results=5,
)
for result in response.results:
print(f"{result.title}: {result.url}")
print(result.snippet)
```
`search_provider` is required and has no default. LiteLLM does not pick a search backend for you, so installing the package is not enough to reach Nimble. An async `asearch()` with the same signature is also available.
#### 3. Or search through the AI Gateway
Register Nimble as a search tool in `config.yaml`:
```yaml theme={"system"}
search_tools:
- search_tool_name: nimble-search
litellm_params:
search_provider: nimble
api_key: os.environ/NIMBLE_API_KEY
```
Start the gateway:
```bash theme={"system"}
litellm --config /path/to/config.yaml
# RUNNING on http://0.0.0.0:4000
```
Then call the search endpoint. The path segment is the `search_tool_name` you registered:
```bash theme={"system"}
curl http://0.0.0.0:4000/v1/search/nimble-search \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{
"query": "latest AI developments",
"max_results": 5
}'
```
Send your LiteLLM virtual key in the `Authorization` header, not your Nimble key. The gateway holds the Nimble credential server-side, so callers never see it.
### Credentials
**Required.** Your Nimble API key. The provider reads it from the environment, or you can pass `api_key=` per call in the SDK and `api_key:` per search tool in the gateway config.
Without it, the provider raises `NIMBLE_API_KEY is not set` before any request leaves your machine.
**Optional.** Overrides the API base. Defaults to `https://sdk.nimbleway.com/v2`, and the provider appends `/search` itself. Set it only to point at a proxy or a regional endpoint.
### Parameters
LiteLLM maps four unified parameters onto Nimble's names, then forwards every other parameter to the Search API untouched.
| LiteLLM parameter | Sent to Nimble as |
| - | - |
| `query` | `query`. A list is joined with spaces, since Nimble takes one string. |
| `max_results` | `max_results`, unclamped. Nimble validates the 1 to 100 range and reports the error. |
| `country` | `country`, upper-cased to the ISO-2 form Nimble expects. |
| `search_domain_filter` | `include_domains`, with `-`-prefixed hosts going to `exclude_domains`. |
| `max_tokens_per_page` | Dropped. Nimble has no equivalent. |
Passing Nimble's own `include_domains` or `exclude_domains` overrides anything derived from `search_domain_filter`.
Anything LiteLLM does not recognize goes into the request body as-is, so Nimble's full parameter surface stays reachable. The values below are what the Search API accepts. LiteLLM does not validate them, so it is not the source of truth for allowed values.
```python theme={"system"}
response = search(
query="latest tech news",
search_provider="nimble",
max_results=5,
focus="general",
search_depth="deep",
time_range="week",
output_format="markdown",
)
```
| Parameter | Accepted values |
| - | - |
| `focus` | `general` (default), `news`, `location`, `coding`, `academic`, `geo`, `shopping`, `social`. Also takes a list of explicit Extract Template names. |
| `search_depth` | `lite`, `fast`, `deep`. Some focus modes accept only `lite` and `deep`: see [Focus Modes](/nimble-sdk/web-tools/search#focus-modes). |
| `time_range` | `hour`, `day`, `week`, `month`, `year`. Cannot be combined with `start_date` or `end_date`. |
| `start_date` / `end_date` | `YYYY-MM-DD` or `YYYY`. |
| `output_format` | `plain_text`, `markdown` (default), `simplified_html`. |
| `content_type` | A **list**, never a string: `["pdf"]`, `["documents", "spreadsheets"]`. Only valid with `focus="general"`. |
| `locale` | Language code such as `en`, `fr`, `de`. Defaults to `en`. |
| `max_subagents` | 1 to 10. Applies to the `shopping`, `social`, and `geo` focus modes. |
Full reference: [Search API](/api-reference/search/search).
### Cost tracking on the gateway
`nimble/search` is priced in LiteLLM's own model cost map, so Nimble spend lands in LiteLLM's spend logs and dashboard alongside your model spend. No extra configuration is needed.
LiteLLM's figure is an estimate, not your invoice. It applies one flat per-query rate to every search, which does not model how Nimble bills:
* Nimble prices Search by `search_depth`, and `lite` costs less per search than `fast`. LiteLLM charges the same figure for both.
* `search_depth="deep"` adds a live extraction surcharge on top, billed at Extract rates for each page scraped in real time.
* Volume plans price differently from pay-as-you-go.
See [Pricing](/nimble-sdk/admin/pricing) for the authoritative rates.
### Additional Resources
The provider reference in LiteLLM's own documentation.
Every parameter the provider can forward, with accepted values.
Focus modes, search depth, and filtering explained.
Reach Extract, Map, and Crawl, which LiteLLM does not cover.
# LlamaIndex
Source: https://docs.nimbleway.com/integrations/connectors/llamaindex
Add real-time web search and cited deep research to LlamaIndex agents and workflows with the Nimble tool specs.
## Overview
The [`llama-index-tools-nimble`](https://pypi.org/project/llama-index-tools-nimble/) package brings Nimble to [LlamaIndex](https://www.llamaindex.ai/). It ships two tool specs, and an agent can hold both at once.
* **`NimbleToolSpec`**: live web search. One query, ranked results in seconds.
* **`NimbleAgentToolSpec`**: deep research on a [Nimble Agent](/nimble-sdk/web-search-agents/overview). One run, a synthesized answer with per-claim citations and confidence.
* **Citable output**: both return LlamaIndex `Document` objects with the source URLs in the document text, so the model can read and cite them.
* **Agent-ready**: `to_tool_list()` plugs either spec into any LlamaIndex agent or workflow.
* **RAG-friendly**: `Document` output drops straight into LlamaIndex indexes and query engines.
## Prerequisites
The package requires `>=3.10,<4.0`.
`llama-index-core>=0.13.0,<0.15` installs automatically. The agent examples also need an LLM integration, such as `llama-index-llms-openai`.
Version 0.2.0 raised the SDK floor from `>=0.16,<1.0.0` to `>=1.2.0,<2.0.0`. If other code in the same environment targets `nimble-python` 0.x, plan to update it at the same time.
Required by both tool specs. Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys), free trial available. The key is passed to the SDK client rather than stored on the tool spec, and the tool's own run errors are built without it.
Deep research runs without one: the API provisions an agent per run. See [Agent identity](#agent-identity).
## Quick Start
```bash theme={"system"}
pip install llama-index-tools-nimble
```
Add an LLM integration to run the agent examples:
```bash theme={"system"}
pip install llama-index-llms-openai
```
Get your key from [Nimble's dashboard](https://online.nimbleway.com/settings/api-keys), then set it as an environment variable:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
Or pass it directly when constructing either tool spec:
```python theme={"system"}
from llama_index.tools.nimble import NimbleToolSpec
tool_spec = NimbleToolSpec(api_key="your-api-key")
```
`max_results` caps how many results come back. The default is 6.
```python theme={"system"}
from llama_index.tools.nimble import NimbleToolSpec
tool_spec = NimbleToolSpec()
documents = tool_spec.search(
"latest developments in web data infrastructure",
max_results=5,
)
for doc in documents:
print(doc.metadata["title"], "-", doc.metadata["url"])
```
```text theme={"system"}
Web Data Infrastructure for AI - https://nimbleway.com/...
The State of Web Data in 2026 - https://example.com/...
```
## How it works
`to_tool_list()` turns a tool spec into LlamaIndex tools. `NimbleToolSpec` exposes `search`, `NimbleAgentToolSpec` exposes `run`.
When the prompt needs live data, the model calls `search` for a quick lookup or `run` for a researched answer.
Results come back as `Document` objects whose text carries the source URLs, so the model writes a grounded, citable answer.
## Search
`NimbleToolSpec` wraps Nimble's [Search API](/nimble-sdk/web-tools/search) and returns each result as a `Document` carrying the page title and source URL.
### Build an AI agent
Pass the tool spec to a LlamaIndex `FunctionAgent` with `to_tool_list()`. The agent can then search the web to answer questions with current facts:
```python theme={"system"}
import asyncio
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI
from llama_index.tools.nimble import NimbleToolSpec
agent = FunctionAgent(
tools=NimbleToolSpec().to_tool_list(),
llm=OpenAI(model="gpt-4o-mini"),
system_prompt="You are a research assistant. Use web search to answer with current facts.",
)
async def main():
response = await agent.run("What has Nimble announced recently?")
print(response)
asyncio.run(main())
```
### Citable Document output
Every result is a LlamaIndex `Document`. The title and source URL are embedded in the document text, so the agent can read and cite the source even though it never sees metadata. Each document's text has this shape:
```text theme={"system"}
URL:
```
The title and URL are also available as metadata fields for programmatic use:
| Field | Description |
| - | - |
| `metadata["title"]` | The page title |
| `metadata["url"]` | The source URL |
### Use results in a LlamaIndex index
Because `search()` returns `Document` objects, results drop straight into a LlamaIndex index for retrieval-augmented generation:
```python theme={"system"}
from llama_index.core import VectorStoreIndex
from llama_index.tools.nimble import NimbleToolSpec
# Fetch live web results as Documents
documents = NimbleToolSpec().search(
"web data infrastructure trends",
max_results=10,
)
# Index them, then query with citable sources
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("Summarize the key trends.")
print(response)
```
### Search options
Argument to `search()`. The search query, supplied by the model when the tool is agent-driven.
Argument to `search()`. Maximum results to return. Type `int`, default `6`, must be at least 1. Nimble's own cap is soft, so the tool trims the list to hold the contract.
`NimbleToolSpec(api_key=...)` is the only constructor argument. Search depth, focus, and output format are fixed at `lite`, `general`, and markdown, and the model cannot change them.
## Deep research with Nimble Agents
`search()` is a synchronous building block: one query, one response, seconds. A [Nimble Agent](/nimble-sdk/web-search-agents/overview) run is a different capability. An autonomous agent plans, searches, reads, and cross-checks many sources, then returns a final answer with per-claim citations and confidence.
| | Search | Nimble Agent run |
| - | - | - |
| Latency | Seconds | 10 to 30 seconds at `low`, 5 to 15 minutes at the default `high`. See [Efforts](/nimble-sdk/web-search-agents/efforts) |
| Endpoint | `/v2/search` | `/v2/agents/*` |
| Output | Ranked results, one `Document` each | One `Document`: final answer plus sources, citations, confidence |
| Best for | Grounding a chat turn | Briefs, due diligence, monitoring, enrichment |
### Run one research task
`NimbleAgentToolSpec` exposes a single `run` tool. It creates the run, polls until the run reaches a terminal status, then fetches and maps the result, all inside one call.
```python theme={"system"}
from llama_index.tools.nimble import NimbleAgentToolSpec
# timeout is raised past the 300 s default, because agent runs outlast it.
agent_tool = NimbleAgentToolSpec(timeout=1800) # reads NIMBLE_API_KEY
doc = agent_tool.run(
"What are the leading approaches to LLM guardrails, and who builds them?",
)
print(doc.text) # final answer + "Sources:" list
print(doc.metadata["confidence"]) # high / medium / low / pre_existing
print(doc.metadata["claims"]) # per-claim citations
print(doc.metadata["web_search_agent_id"]) # the agent the API ran this on
```
Hand it to a LlamaIndex agent the same way as the search spec. Tell the model the tool is slow but thorough, so it reaches for it deliberately:
```python theme={"system"}
import asyncio
import os
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI
from llama_index.tools.nimble import NimbleAgentToolSpec
tool_spec = NimbleAgentToolSpec(
agent_id=os.environ.get("NIMBLE_AGENT_ID"),
timeout=1800,
)
agent = FunctionAgent(
tools=tool_spec.to_tool_list(),
llm=OpenAI(model="gpt-4o-mini"),
system_prompt=(
"You are a research assistant. For questions that need a researched, "
"synthesized answer, call the Nimble research tool (it is slow but "
"thorough) and relay its answer together with the source URLs it cites."
),
)
async def main():
response = await agent.run(
"Research: what do photography reviewers consider the best "
"mirrorless cameras for travel, and why? Cite your sources."
)
print(response)
asyncio.run(main())
```
The model-facing tool schema is `{task, output_schema, input_data, sources}`. Agent identity, credentials, effort, and the deadline are set by the application at construction, so the model can never choose the agent or raise the cost tier.
### Agent identity
`agent_id` is optional, and the choice decides the route:
| Configuration | Route | Behavior |
| - | - | - |
| `agent_id` omitted | `POST /v2/agents/runs` | The API provisions an agent for the run and returns its id |
| `agent_id="wsa_..."` | `POST /v2/agents/{agent_id}/runs` | The run executes on that preconfigured agent |
Either way the **returned** `web_search_agent_id`, not the configured one, is the authority for the polling and result calls that follow. It lands in the document metadata, so an auto-provisioned agent stays reachable afterwards. A returned id that differs from a configured one is rejected rather than papered over.
The tool is execution-only by design. It never creates, edits, or deletes agent instances, so account administration stays off the LLM surface. To pin runs to an agent, provision it in the [dashboard](https://online.nimbleway.com) or through `POST /v2/agents`, then pass its `wsa_...` id.
### Structured output and enrichment
Three optional per-run controls shape the answer. They are passed through unchanged: the adapter never rewrites a caller's schema or source guidance.
```python theme={"system"}
doc = agent_tool.run(
"Find the founding year and headquarters for this company.",
input_data={"company": "Acme", "domain": "acme.example"},
output_schema={
"type": "object",
"properties": {"founded": {"type": "integer"}, "hq": {"type": "string"}},
},
sources={"prioritize": "regulatory filings", "avoid": "press releases"},
)
```
A JSON Schema object the answer must match. Supply it to get structured JSON back in place of prose. The document text then holds pretty-printed JSON, and `metadata["output_type"]` reads `json`.
An object, or a list of objects, to enrich. Each row holds known data about one entity to research. Enrichment payload only: it is never stored on the agent.
Source guidance. `allow` and `block` take lists of source objects; `prioritize` and `avoid` take free text. Any other key is rejected, so a model filling this tool schema cannot smuggle `skill`, `use_case`, or `agent_name` through a nested dict.
The schema above returns this shape, prose replaced by pretty-printed JSON and the `Sources:` list kept:
```text theme={"system"}
{
"hq": "",
"founded":
}
Sources:
- — (primary)
- — (secondary)
```
### Tool spec options
Set on the `NimbleAgentToolSpec` constructor. Every parameter is optional.
The Nimble Agent to run on, in the form `wsa_...`. Type `str`, default `None`, which lets the API provision an agent per run.
Nimble API credentials. Falls back to `NIMBLE_API_KEY`.
`'low'`, `'medium'`, `'high'`, `'x-high'`, or the gated `'max'`. Default `None`, which omits the field so Nimble applies the agent or template default of `high`. Higher tiers research more sources and take longer. See [Efforts](/nimble-sdk/web-search-agents/efforts). This is an application setting, not a tool argument the model can raise.
Treatment for gated values: `'reject'` (default) or `'degrade'`. `max` is a coming-soon custom-budget capability. Under `reject`, `effort="max"` raises before any run is created and points at the [Nimble product team](https://www.nimbleway.com/contact). Under `degrade`, the effective tier becomes `x-high` and a warning announces the substitution, so a different tier is never billed silently.
Overall deadline in seconds for one `run` call, covering creation, polling, and result retrieval. Type `float`, default `300.0`. Raise it: the default is shorter than a typical run. See [Long runs and deadlines](#long-runs-and-deadlines).
Seconds between status polls, and the pause before re-attempting a transient failure. Type `float`, default `10.0`. Shorter values are intended only for tests.
Typed SDK hints applied to the run. `agent_name` names the run's generated agent, `skill` is a skill identifier, and `use_case` is `'research'`, `'enrichment'`, or `'dataset_building'`. All default to `None`.
### Results and citations
A completed run returns one `Document`. Its text is the final answer followed by a `Sources:` list, because an agent only ever sees a tool's stringified output, where document metadata is dropped. Citations have to live in the text for the model to read them.
```text theme={"system"}
# Leading Approaches to LLM Guardrails
LLM guardrail approaches are organized around a multi-layered safety pipeline
spanning the model's lifecycle [1]:
## 1. Input-Level Defenses (Prompt Screening)
Pre-processing mechanisms block or modify disallowed prompts before they reach
the LLM, typically using classifiers, keyword/regex matching, or prompt-injection
detectors [2].
...
Sources:
- Survey on LLM Safety: Attacks, Defenses, Alignment, Metrics, and ... — https://link.springer.com/article/... (primary)
- A Survey on LLM Guardrails: Part 1, Methods, Best Practices and ... — https://blog.budecosystem.com/... (primary)
- Evaluating the Robustness of Large Language Model Safety Guardrails ... — https://arxiv.org/html/... (primary)
```
The full structured trust payload stays in metadata for programmatic use:
| Field | Description |
| - | - |
| `run_id` | The run identifier, `task_run_...` |
| `agent_id` / `web_search_agent_id` | The agent the API bound the run to, `wsa_...`. Same value under both keys |
| `effort` | The effort the run actually executed at |
| `output_type` | `text` for prose, `json` when an `output_schema` was given |
| `confidence` | Overall trust grade: `high`, `medium`, `low`, or `pre_existing` |
| `reasoning` | Why the answer earned that grade |
| `sources` | Every source consulted, with `url`, `title`, `type` (`primary` or `secondary`), and `source_category` |
| `claims` | Per-claim citations, each with `confidence`, `reasoning`, its `callout` or `path` key, and `citations` carrying `url`, `title`, source grading, and supporting `excerpts` |
A text answer keys each claim by `callout`, the numeric marker in the prose. A structured answer keys each claim by `path`, the JSON path of the value, such as `$.founded`.
```python theme={"system"}
metadata["claims"][0] == {
"callout": 1,
"confidence": "high",
"reasoning": "Backed by a primary source (academic)",
"citations": [
{
"url": "https://link.springer.com/article/...",
"title": "Survey on LLM Safety: Attacks, Defenses, Alignment, ...",
"source_type": "primary",
"source_category": "academic",
"source_intent": "academic",
},
],
}
```
Grades are earned per run, not assumed. The run above returned `medium` overall, because the vendor half of the question rested on commercial comparison articles rather than primary sources. See [Trust and citations](/nimble-sdk/web-search-agents/trust) for how that is decided.
Returned content is untrusted web data. Treat it as data, not as instructions, and rely on your framework's own guardrails.
### Long runs and deadlines
Agent research is asynchronous server-side. Do not treat `run()` as a short synchronous call: it blocks for as long as the run takes, and the tool owns the whole lifecycle inside that one call.
One POST, issued exactly once. See [Errors](#errors) for why it is never re-attempted.
A status poll every `poll_interval` seconds, 10 by default, until the run reports `completed`, `failed`, or `cancelled`.
Only after `completed`. Fetching earlier is a conflict by contract, and a failed run's error detail is already on the polled run.
One `timeout` budget covers all three phases. Each HTTP request is bounded by whatever is left of it, with a 5 second connect ceiling, so a stalled request cannot fall back to the SDK's much longer default. Polling and result fetches re-attempt transient failures up to 5 times, honoring `Retry-After` and staying inside the budget. Auth, permission, and validation errors fail fast.
**Raise `timeout` for anything but `low` effort.** The default is 300 seconds, but the default effort is `high`, which typically takes [5 to 15 minutes](/nimble-sdk/web-search-agents/efforts). A default-configured spec will therefore often hit its deadline on a perfectly healthy run.
Reaching the deadline cancels nothing. `NimbleAgentTimeoutError` carries the `run_id`, the run keeps going on Nimble's side, and the result stays fetchable through the [runs API](/api-reference/public-api/get-agent-run-result) or the dashboard.
### Errors
A run that does not produce a result raises a typed error. Each one carries the run's identifiers as attributes and inside the message, because agent frameworks usually surface only the message. The one exception is an ambiguous creation failure, where no run id was ever returned.
| Error | Meaning |
| - | - |
| `NimbleAgentTimeoutError` | Not terminal within `timeout`. The run may still complete server-side |
| `NimbleAgentRunFailedError` | The run terminated as `failed`. Carries the server's error message |
| `NimbleAgentRunCancelledError` | The run terminated as `cancelled` |
| `NimbleAgentProtocolError` | Unknown status, malformed result, response identity mismatch, or persistent polling and result errors. The SDK exception is chained |
| `NimbleAgentCreateAmbiguousError` | Run creation failed without a definite outcome. `run_id` is `None`, and `status_code` carries the HTTP status when there was one |
All five subclass `NimbleAgentRunError`, so one `except` clause catches every run failure:
```python theme={"system"}
from llama_index.tools.nimble import (
NimbleAgentRunError,
NimbleAgentTimeoutError,
NimbleAgentToolSpec,
)
# x-high typically takes 15 to 30 minutes, so the deadline allows for that.
agent_tool = NimbleAgentToolSpec(effort="x-high", timeout=2400)
try:
doc = agent_tool.run("Map the EU AI Act enforcement timeline.")
except NimbleAgentTimeoutError as error:
# Not a failure. Persist the ids however your app stores state, then
# fetch the run later. `save_run_for_later` here is your own function.
save_run_for_later(error.run_id, error.agent_id)
except NimbleAgentRunError as error:
print(error.status, error.run_id, error.agent_id)
raise
```
`NimbleAgentCreateAmbiguousError` is the one error not to retry. Creation is sent once and never re-sent, so a run may already be underway with no id returned. A function-calling model reads a bare timeout as an ordinary transient, so exclude this error from any automatic tool-retry policy. Look for the earlier run in the [dashboard](https://online.nimbleway.com) instead. Requests that were definitely rejected, such as a bad key or a validation error, are safe to call again once fixed.
## Limitations
* **Search and Nimble Agent runs** ship in this package. Extract, Map, and Crawl are not exposed as tool specs. Use the [Nimble Python SDK](/nimble-sdk/sdks/python).
* **One blocking call per run.** There is no start-now, collect-later split, and no run event streaming (SSE). A run that outlives `timeout` is recovered through the runs API, not through this tool.
## Related features
Easily confused with this package:
| Feature | Where it lives |
| - | - |
| Agents, `/v2/agents/*` | This page. Current deep-research agents |
| Extract Templates, `/v2/extract/templates` | The [Extract Template guide](/nimble-sdk/web-tools/extract/template). The old `/v1/agent` site scrapers were renamed to these, and are unrelated to Nimble Agents |
| LlamaHub listing | The package carries LlamaHub registry metadata for both tool specs. No effect on how you install or import |
| LlamaParse | LlamaIndex's own document parser. Not a Nimble product and not related to these tool specs |
## Resources
`llama-index-tools-nimble` on PyPI.
Source, README, and runnable examples.
Deep research agents and agentless runs.
The deep-research capability behind the `run` tool.
Nimble's underlying search capability.
Direct access to Extract, Map, Crawl, and agent administration.
# Mastra
Source: https://docs.nimbleway.com/integrations/connectors/mastra
Give a Mastra agent deep research on the live web. Start a Nimble Agent run in one turn, collect the cited answer in another, from any process.
## Overview
Give a Mastra agent the ability to research the live web. Ask a question in one sentence. Get back a written answer with sources, per-claim citations, and a confidence grade.
The [`@nimble-way/mastra`](https://www.npmjs.com/package/@nimble-way/mastra) package brings [Nimble Agent](/nimble-sdk/web-search-agents/overview) research into [Mastra](https://mastra.ai) as three typed tools. Nimble plans the research, searches, and opens pages. It reads JavaScript-heavy sites, cross-checks what it finds, and returns a cited answer. Your agent sends a task and collects the result.
That research takes minutes, not milliseconds. A `high` effort run takes 5 to 15 minutes, far longer than a single agent turn. So by default none of the three tools block: the run keeps going server-side and the `runId` is the only handle needed to collect it. Bounded waiting is opt-in, and the `wait` option is the one way to make the result tool block.
* **A cited answer from the live open web**: prose or JSON, with sources, per-claim citations with verbatim excerpts, and confidence grades, read at run time.
* **Gets cheaper the more you run it**: a reused Nimble Agent remembers which sources worked. Recurring research runs up to 50% cheaper.
* **Start now, collect later**: start the run in one turn, read the result in another turn, another process, or another day.
* **The model never sees your credential**: the API key is resolved server-side and sent only as a request header.
### Which path to take
Two ways to get Nimble research into a Mastra agent. Pick one in ten seconds.
The fastest start. No package to install. Tools arrive over the wire and the model calls them. Gives you the full Nimble tool surface, not just research.
Typed tools, a developer-controlled effort ceiling, server-side output schemas, and explicit create-failure semantics. Best when research is the job and cost control matters.
## Prerequisites
The package declares `engines.node >= 22.13.0`.
Peer dependencies, supplied by your project: `@mastra/core` `^1.56.0` and `zod` `^3.25.1 || ^4.0.0`.
The `zod` floor is `3.25.1` rather than `3.25.0` for a reason. `3.25.0` declares a `./v4` export whose target is missing from its published tarball, and Mastra's schema layer resolves that path, so the combination fails at import. The declared range excludes it, so npm refuses the install rather than letting it fail at run time.
Verified against the published `0.1.1`: `zod` `3.25.1`, `3.25.76`, and `4.0.0` all work with `@mastra/core` `1.56.0`, as does `4.5.4` with `1.64.0`.
Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys). See [Handling the API key](#handling-the-api-key) for the rules that keep it out of model input.
Mastra resolves the agent's `model` at run time, so the provider needs its own key in the environment. The examples here use `anthropic/claude-sonnet-4-6`, which reads `ANTHROPIC_API_KEY`.
This is separate from `NIMBLE_API_KEY`. Nimble does the research; your model decides when to ask for it.
Create a Nimble Agent once, then reuse it. Its ID looks like `wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9`.
A reused agent keeps its memory, so it gets faster, more accurate, and up to 50% cheaper on recurring work. Create one in the [dashboard](https://online.nimbleway.com) or with `POST /v2/agents`.
The agent is developer configuration, not model input. The model can never choose which agent to run.
## Quick Start
```bash theme={"system"}
npm install @nimble-way/mastra@0.1.1 @mastra/core zod
```
`0.1.1` is the current release. It is published from CI over OIDC and carries a SLSA provenance attestation, so the artifact on npm can be traced to the commit and workflow that built it.
```bash theme={"system"}
export NIMBLE_API_KEY="your-key"
export NIMBLE_AGENT_ID="wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9"
```
Both are read server-side at execute time. Never put either in a committed file.
```ts src/agents/researcher.ts theme={"system"}
import { Agent } from "@mastra/core/agent";
import { createNimbleAgentTools } from "@nimble-way/mastra";
export const researcher = new Agent({
id: "researcher",
name: "Researcher",
instructions:
"For deep research questions, start a Nimble agent run, tell the user the " +
"runId, and fetch the result when they ask for it later.",
model: "anthropic/claude-sonnet-4-6",
tools: createNimbleAgentTools(),
});
```
`createNimbleAgentTools()` returns all three tools sharing one credential and one agent configuration. Pass `{ agentId, apiKey }` to override the environment.
```ts theme={"system"}
const result = await researcher.generate(
"Summarize how the EU AI Act is being enforced in 2026, with sources.",
);
console.log(result.text);
```
The model calls `nimble-agent-start-run` and answers in seconds with a `task_run_…` ID, which it reports in `result.text`. The research keeps going server-side for minutes. Collect it later with the same ID.
### Example Response
A completed run, as the result tool returns it. This is the prose shape. An agent with a stored output schema returns `"type": "json"` with a `json` field instead of `text`, and its claims carry `path` rather than `callout`:
```json theme={"system"}
{
"ready": true,
"runId": "task_run_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
"agentId": "wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"status": "completed",
"effort": "low",
"createdAt": "2026-09-08T10:00:00Z",
"startedAt": "2026-09-08T10:00:01Z",
"completedAt": "2026-09-08T10:02:00Z",
"output": {
"type": "text",
"text": "The answer, with citation markers [1].",
"trust": {
"confidence": "high",
"reasoning": "Two primary sources agree.",
"sources": [
{
"url": "https://example.com/a",
"type": "primary",
"source_category": "official",
"title": "Official doc"
}
],
"claims": [
{
"callout": 1,
"confidence": "high",
"reasoning": "Stated directly in the source.",
"citations": [
{
"url": "https://example.com/a",
"excerpts": ["verbatim excerpt here"],
"source_type": "primary"
}
]
}
]
}
}
}
```
## How it works
`nimble-agent-start-run` sends `POST /v2/agents/{agent_id}/runs` and returns immediately with a real `task_run_…` ID. The turn is never blocked for the run's duration.
Nimble plans the research, searches, opens pages, and cross-checks findings. Minutes, not milliseconds.
`nimble-agent-run-status` returns an instant snapshot. `nimble-agent-run-result` returns the answer, or `{ ready: false }` while the run is still working.
A completed run returns prose or structured JSON, plus `trust`: sources, per-claim citations with verbatim excerpts, and confidence grades.
### The three tools
| Tool id | Model-facing input | What it returns |
| - | - | - |
| `nimble-agent-start-run` | `task` (required), `effort` (optional) | `runId`, `agentId`, `interactionId`, `status`, `effort`, `createdAt` |
| `nimble-agent-run-status` | `runId` | `status`, `isActive`, and run timestamps. Instant, never waits |
| `nimble-agent-run-result` | `runId` | The answer with `trust`, or `{ ready: false }` while still working |
The factory returns them keyed `nimbleAgentStartRun`, `nimbleAgentRunStatus`, and `nimbleAgentRunResult`.
Those three inputs are the complete model-facing surface. The credential, the agent, the effort ceiling, the output schema, and the source boundary are all developer configuration.
### The resumable lifecycle
The `runId` is the only handle. Any process configured with the same agent can check or collect a run it did not start.
```ts theme={"system"}
// Turn 1 - the agent starts a run and answers immediately:
// start -> { runId: "task_run_…", status: "queued", … }
// Turn 2 (seconds or hours later, same or a different process):
// status -> { status: "running", isActive: true, … }
// Turn 3:
// result -> { ready: false, status: "running", … } // still working, not an error
// result -> { ready: true, status: "completed", output: { type: "text", text, trust } }
```
## Parameters
The Nimble Agent to run, for example `wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9`. Defaults to `process.env.NIMBLE_AGENT_ID`. Resolved at execute time, so the model can never choose the agent.
Missing it raises `NimbleConfigError: Missing Nimble agent id`.
Nimble API key. Defaults to `process.env.NIMBLE_API_KEY`. Read server-side at execute time and sent only as a request header.
Missing it raises `NimbleConfigError: Missing Nimble API key`.
`effort` is the tier used when the model does not choose one. Leave it unset and the agent instance's own configured default applies, which is the recommendation.
`effortCap` is the ceiling on what the *model* may request. It defaults to `high`, so a model cannot unilaterally reach the `x-high` and `max` cost tiers. A developer-set `effort` is not clamped.
See [effort tiers](/nimble-sdk/web-search-agents/efforts) for what each tier does and costs.
Verified behaviour, reading the request body the package sends:
| Configuration | Model requests | Sent to the API |
| - | - | - |
| default cap (`high`) | `max` | `high` |
| default cap (`high`) | `x-high` | `high` |
| `effortCap: "medium"` | `max` | `medium` |
| `effortCap: "medium"` | `low` | `low` |
| `effort: "max"`, `effortCap: "medium"` | nothing | `max` |
| nothing set | nothing | `effort` omitted |
Those are the request bodies the package sends. `max` is a custom-budget tier that is [coming soon](/nimble-sdk/web-search-agents/efforts), so the API may reject it even though the package forwards it. Ask for `x-high` if you want the top available tier now.
A JSON Schema sent as the Agent API `output_schema`. Use it when the run must return structured data.
Keep this in developer configuration. Do not ask the model to author JSON, source URLs, or excerpts in its prompt.
```ts theme={"system"}
import { nimbleAgentStartRunTool } from "@nimble-way/mastra";
const startResearch = nimbleAgentStartRunTool({
outputSchema: {
type: "object",
required: ["facts", "recommendation"],
properties: {
facts: { type: "array", items: { type: "object" } },
recommendation: { type: "string" },
},
},
});
```
A completed run then returns `output.type: "json"` with `output.json` instead of `output.text`.
Server-enforced source guidance, sent as the Agent API `sources`.
```ts theme={"system"}
import { nimbleAgentStartRunTool } from "@nimble-way/mastra";
const startResearch = nimbleAgentStartRunTool({
sources: {
allow: [
{ title: "Official documentation", domains: ["docs.example.com"] },
{ title: "Official repository", domains: ["github.com"] },
],
},
});
```
Rejected before the run is created: an empty `allow`, a group with a blank `title`, a group with an empty `domains` array, and a blank domain string.
Opt-in bounded waiting on the result tool. Absent, which is the default, the tool never blocks.
```ts theme={"system"}
import { nimbleAgentRunResultTool } from "@nimble-way/mastra";
const result = nimbleAgentRunResultTool({
wait: { pollIntervalMs: 10_000, timeoutMs: 600_000 },
});
```
Defaults are `timeoutMs: 300_000` and `pollIntervalMs: 10_000`, with a 100 ms floor. Runs take minutes, so polling faster only spends requests. Waiting respects Mastra's per-call `AbortSignal`.
When the timeout elapses the tool returns `{ ready: false }` and the run keeps going server-side. A bounded wait never invents a terminal status: it returns the last authoritative one. If the initial status request itself reaches the deadline, `status` is `unknown` and activity is omitted, because no lifecycle snapshot was received.
Options forwarded to the Nimble client: `baseURL`, `fetch`, `timeout`, and `maxRetries`.
`maxRetries` applies to status and result reads only. Run creation is always sent with `maxRetries: 0`, whatever this value is.
### Per-tool configuration
Individual factories are exported when the three tools need different settings:
```ts theme={"system"}
import {
nimbleAgentStartRunTool,
nimbleAgentRunStatusTool,
nimbleAgentRunResultTool,
} from "@nimble-way/mastra";
const tools = {
startResearch: nimbleAgentStartRunTool({ effortCap: "medium" }),
researchStatus: nimbleAgentRunStatusTool(),
researchResult: nimbleAgentRunResultTool({ wait: { timeoutMs: 600_000 } }),
};
```
## A long-running research example
This is the shape the package is built for. The run outlives the turn that started it.
```ts src/research.ts theme={"system"}
import { Agent } from "@mastra/core/agent";
import { createNimbleAgentTools } from "@nimble-way/mastra";
export const researcher = new Agent({
id: "researcher",
name: "Researcher",
instructions:
"Start a Nimble run for deep research questions and report the runId to the " +
"user. When the user asks for the answer, fetch the result by runId. If the " +
"result is not ready, say so and keep the runId. Never start a second run " +
"for a task that already has a runId. If starting a run fails without " +
"returning a runId, report the failure and stop. Do not try again.",
model: "anthropic/claude-sonnet-4-6",
tools: createNimbleAgentTools({ effortCap: "high" }),
});
// Turn 1: the model starts the run and answers in seconds.
const started = await researcher.generate(
"Summarize how the EU AI Act is being enforced in 2026, with sources.",
);
console.log(started.text); // carries the task_run_… id persist it
// Turn 2, minutes later, in the same process or a different one:
const collected = await researcher.generate(
"Fetch the result for run task_run_9f8e7d6c5b4a39281706f5e4d3c2b1a0.",
);
console.log(collected.text);
```
Persist the `runId` wherever your application keeps state. It is the only handle needed, and it stays valid across processes and restarts.
Keep the "never start a second run" clause in the instructions. A bounded `wait` can expire before a `high` run completes, and the result tool returns `{ ready: false }` whenever it does. That is expected, not a failure. Without the clause, restarting is an available response to a not-ready result, and each restart is a new billed run. Nothing in `effortCap` limits how many runs a model may create.
The three tools are built for the model to call. To drive the lifecycle yourself, server-side and without a model in the loop, use the exported `createNimbleClient(apiKey)` and call `nimble.agents.runs.create`, `.get`, and `.result` directly.
## Citations and trust metadata
Every completed run carries a `trust` object:
* `confidence` and `reasoning`: the run's own grade of its answer, and why.
* `sources`: every source used, with `type` and `source_category`.
* `claims`: per-claim confidence, plus `citations` carrying the `url` and the **verbatim excerpts** the claim rests on. In a prose answer, a `callout` number ties the claim to its marker in the text. Under an [output schema](#parameters) there is no prose, so claims carry a `path` instead: the JSON path of the value they support. Read whichever is present, not `callout` alone.
`trust` is passed through verbatim. `snake_case` field names are preserved and unknown future fields are kept, so nothing is silently dropped and prose citation markers stay aligned with the text.
Trust-provided source URLs, claims, citations, and excerpts are the authoritative record. Render them from `trust`. Never ask the model to restate them as its own output fields, and never treat a model-authored URL as a citation.
## Handling the API key
`NIMBLE_API_KEY` is a server-side credential. The package resolves it at execute time and sends it only as a request header.
Verified against the published package, reading the requests it actually sends: the key appears in `Authorization: Bearer …` and nowhere else. It is absent from all three tool input schemas, absent from tool outputs, and scrubbed from error messages.
The key must never appear in a model schema, a tool input, a tool output, client state, a log line, a prompt, or an example. The model's entire input surface is `task`, `effort`, and `runId`. Keep it that way.
Do not pass the key through a URL. A credential in a URL leaks into server access logs, browser history, and `Referer` headers sent to third parties.
If you inject your own client with `client`, attribution headers and credential handling become your concern. Pass the matching `apiKey` too, or server error details are withheld, because the package cannot prove they are safe to expose.
## Failure and not-ready behaviour
An active run is never an error. Two cases matter, and the package treats them differently on purpose.
The result tool returns `{ ready: false }` with the run's `status` and `isActive`. Not an error. Check again later.
**Branch on `ready`, never on `status`.** A not-ready payload can carry any status, including a terminal one, and it never carries `output`. The same HTTP 409 on a completed run reports differently depending on `wait`:
| Configuration | Returned |
| - | - |
| no `wait` | `ready: false`, `status: "running"`, `isActive: true` |
| with `wait` | `ready: false`, `status: "completed"`, `isActive: false`, no `output` |
So `if (status === "completed") render(output.text)` throws on a payload that is entirely legal. A `status` reported after a `wait` deadline is also the last snapshot taken rather than a fresh read, so a run that failed after that snapshot can still report `running`. Confirm with the status tool, which never waits, before treating a run as alive.
Throws `NimbleAgentRunError` with `reason: "failed"`, the server's message, and the `runId` preserved so the run can still be inspected or reported.
Verified message shape: `Nimble agent run task_run_… failed: `.
A definite 4xx such as 429 gives `createOutcome: "not-created"` and `reason: "request"`. No run exists. Retry only after the underlying condition clears.
A timeout, 408, 409, 5xx, or a dropped connection gives `createOutcome: "unknown"`. The run **may** have been created server-side.
**An ambiguous create is never retried automatically.** Run creation is billed and not idempotent, so the POST is sent once with `maxRetries: 0`. The transient-retry policy is disabled for creation only. Status and result reads keep normal retries.
Reconcile before creating again: list recent runs for the agent, or check the dashboard. An automatic retry here bills a second run.
The thrown error carries **no `runId`**, because none was returned, so a run that was created cannot be reconciled by ID. Reconcile by agent and timestamp instead.
`maxRetries: 0` only removes the *transport* retry. A model that is told to retry a failed start reinstates it one layer up, and the package cannot tell that apart from a new task. Keep the "do not try again" clause in the [example instructions](#a-long-running-research-example).
Surfaces as `reason: "protocol"` on `NimbleAgentRunError`, not a `TypeError`.
## Mastra background tasks
Mastra can run a tool call as a [background task](https://mastra.ai/docs/long-running-agents/background-tasks), so the agent turn returns before the tool finishes. This is optional. By default the Nimble tools already return immediately. Reach for it when you want Mastra to hold the thread open and re-invoke the agent once the answer lands.
Enable it on the Mastra instance, then opt the tool in:
```ts theme={"system"}
import { Mastra } from "@mastra/core";
export const mastra = new Mastra({
agents: { researcher },
backgroundTasks: {
enabled: true,
globalConcurrency: 10,
perAgentConcurrency: 5,
},
});
```
With `untilIdle`, the stream stays open until the background task completes and the agent is re-invoked with the result:
```ts theme={"system"}
const stream = await researcher.stream("Research the EU AI Act enforcement record", {
memory: { thread: "t1", resource: "u1" },
untilIdle: true,
});
```
You can also look a task up directly:
```ts theme={"system"}
const task = await mastra.backgroundTaskManager?.getTask(taskId);
const listed = await mastra.backgroundTaskManager?.listTasks({
status: "running",
agentId: "researcher",
});
const tasks = listed?.tasks ?? [];
```
Mastra's `taskId` and Nimble's `runId` are different identifiers for different things. The `taskId` tracks Mastra's local execution of the tool call. The `runId` is the research run itself, and it is the only handle that survives a restart, a redeploy, or a move to another process.
Persist and surface the `runId`. A Mastra `taskId` cannot collect a Nimble run, and a task that fails or is evicted does not stop or reclaim the run it started.
## Nimble research or Mastra's built-in web search
Mastra ships its own `webSearchTool`. It is not an alternative to Nimble research, and the difference is worth ten seconds of thought.
| | Mastra `webSearchTool` | Nimble Agent run |
| - | - | - |
| Who searches | The active model provider | Nimble, independent of your model |
| Provider support | OpenAI, Anthropic, Google, and xAI only | Any model, including local and open-weight |
| Shape | One in-turn search, milliseconds to seconds | A planned multi-step run, minutes |
| Evidence | Whatever the provider returns | Sources, per-claim citations with verbatim excerpts, confidence grades |
| Reuse | None | A reused agent keeps memory across runs |
| Control | Provider-defined | Developer-set effort ceiling, `output_schema`, source allowlist |
`webSearchTool` resolves the active model at runtime and delegates to that provider's own search. When the provider has none, it throws a `MastraError` with the id `WEB_SEARCH_UNSUPPORTED_PROVIDER`.
Reach for `webSearchTool` for a quick in-turn lookup on a supported provider. Reach for a Nimble run when the answer has to be researched rather than recalled, has to carry its evidence, or has to be reproducible across models. They coexist: attach both and let the instructions route between them.
## Using the Nimble MCP Server instead
If you want Nimble tools in a Mastra agent without adding a dependency, connect the [Nimble MCP Server](/integrations/mcp-server/mcp-server) over Streamable HTTP:
```ts theme={"system"}
import { MCPClient } from "@mastra/mcp";
const mcp = new MCPClient({
servers: {
nimble: {
url: new URL("https://mcp.nimbleway.com/mcp"),
requestInit: {
headers: { Authorization: `Bearer ${process.env.NIMBLE_API_KEY}` },
},
},
},
});
const tools = await mcp.listTools();
```
Pass the credential in the `Authorization` header, through `requestInit`, never in the URL. A key in a URL leaks into server access logs, browser history, and `Referer` headers sent to third parties. Header values are not logged the same way.
This is the shortest path, and it gives you the whole Nimble tool surface rather than research alone. What it does not give you is the package's developer-side controls. Those are the effort ceiling that stops a model reaching `max`, the server-enforced output schema and source allowlist, and the explicit `not-created` against `unknown` distinction on a failed create.
Choose the MCP server for an interactive agent that needs breadth. Choose the package when research is the job and you need those controls.
The MCP server page covers the full transport options, the authentication options including OAuth, and the complete tool table. This section does not repeat them.
## Attribution
Every request the package makes carries `X-Client-Source: mastra`. The package sets this itself, on run creation and on every status and result read. There is nothing to configure, and nothing for you to set.
The value is a constant. It carries no credential and no user-identifying data.
The one exception is an injected `client`. The package then makes no requests of its own, so attribution travels only if your client sets the header.
## Next Steps
What a Nimble Agent is, how effort tiers work, and how agent memory makes recurring research cheaper.
Transports, the full authentication options including OAuth, and the complete tool table.
How sources, per-claim citations, excerpts, and confidence grades are produced.
Release notes, the typed exports, and the changelog.
# NVIDIA AI-Q
Source: https://docs.nimbleway.com/integrations/connectors/nvidia-aiq
Nimble ships as a built-in web search tool in the NVIDIA AI-Q Blueprint: live web context for enterprise research agents, configured in one YAML block.
### Overview
The [NVIDIA AI-Q Blueprint](https://github.com/NVIDIA-AI-Blueprints/aiq) is an open reference implementation for enterprise research agents. It connects agents to internal data, reasons over it with NVIDIA-served models, and returns sourced business insight. It runs on the NeMo Agent Toolkit, so its tools are `nat` functions declared in a workflow YAML.
Nimble ships as the `nimble_web_search` function, a built-in data-source plugin. Calls go to Nimble's [Search API](/nimble-sdk/web-tools/search) and come back as `` blocks the agent can cite. No fork, no custom tool code.
The provider ships in **AI-Q v2.2.0 and later**.
### What the integration unlocks
Any AI-Q agent gains live web retrieval without tool-specific code. The model passes a natural-language question, and the provider returns ranked results as escaped XML:
```xml theme={"system"}
CUDA Toolkit Documentation
Find installation instructions, launch highlights, programming guides, compiler
documentation, API references, CUDA libraries, profiling tools, samples, and ...
```
Every block carries an absolute `href`, and the provider drops results without one. Nimble handles anti-bot evasion, JavaScript rendering, and geo-targeting underneath.
### Quick Start
#### 1. Get a Nimble API key
Get your API key from [Nimble's dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available) and export it in the environment AI-Q runs in:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
Set this before starting AI-Q. Without it the tool still registers and logs one
warning, then returns an error the first time the agent searches. Startup looks
healthy while the agent cannot search, so treat that warning as a failure.
#### 2. Install AI-Q with the provider
```bash theme={"system"}
git clone https://github.com/NVIDIA-AI-Blueprints/aiq.git && cd aiq
./scripts/setup.sh
```
`setup.sh` creates the virtual environment and installs every data-source plugin, `nimble_web_search` included.
To add it to a checkout you already have:
```bash theme={"system"}
uv pip install -e ./sources/nimble_web_search
```
#### 3. Add the function to your workflow
Declare `nimble_web_search` under `functions` in your workflow YAML:
```yaml theme={"system"}
functions:
web_search_tool:
_type: nimble_web_search
max_results: 5
search_depth: lite
country: US
locale: en
```
Then reference `web_search_tool` from the agent that should search.
#### 4. Confirm the tool registered
```bash theme={"system"}
nat info components --types function | grep nimble_web_search
```
A row for `nimble_web_search` means the plugin entry point resolved.
### Configuration
**Optional in config, required in practice.** Falls back to the `NIMBLE_API_KEY` environment variable. A key set here is passed straight to the SDK and never written to the process environment.
**Optional.** Results to return, `1` to `100`. Defaults to `5`. Sent unclamped, so Nimble validates the range and reports the error.
**Optional.** `lite`, `fast`, or `deep`. Defaults to `lite`.
* `lite`: title, URL, and description only. Lowest latency and lowest token cost.
* `fast`: rich content at low latency, and the depth Nimble recommends for agent loops.
* `deep`: full page content per result. Returns the raw page, including navigation and image data, so pair it with `max_content_length`.
See [Search Depth](/nimble-sdk/web-tools/search-depth) for the tradeoffs and [Pricing](/nimble-sdk/admin/pricing) for what each depth costs.
**Optional.** Defaults to `general`. Leave it there for research and general agent use.
The provider accepts `general`, `news`, `location`, `shopping`, `geo`, and `social`, and validates the value at config load, so a typo surfaces at startup with the valid set named in the error.
`focus` is a workflow setting, not an agent-chosen parameter. The model only supplies a query, so a general research question cannot silently reroute.
**Optional.** Characters kept per result, truncated with an ellipsis. Defaults to `10000`. Set to `None` to disable truncation. Matters most with `search_depth: deep`.
**Optional.** Attempts on a transient failure, `1` to `10`. Defaults to `3`, with exponential backoff capped at 30 seconds. Authentication and authorization errors return immediately instead of retrying.
**Optional.** `country` is an ISO 3166 code for geo-targeted results, default `US`. `locale` is a language code, default `en`.
NVIDIA maintains the full parameter table in its own [configuration reference](https://docs.nvidia.com/aiq-blueprint/latest/customization/configuration-reference.html#nimble-web-search).
### Additional Resources
NVIDIA's own parameter table for `nimble_web_search`.
The blueprint, the provider source, and its tests.
The endpoint behind the provider: parameters and response format.
Reach Extract, Map, and Crawl over the Model Context Protocol.
# OGX
Source: https://docs.nimbleway.com/integrations/connectors/ogx
Nimble is a built-in web_search provider in OGX, the open agentic API server: grounded, live web search for any model it serves.
### Overview
[OGX](https://github.com/ogx-ai/ogx) is an open, production-ready agentic API server and the successor to Llama Stack. It runs the agentic loop (inference, tool calling, RAG, and conversation management) behind the native APIs of OpenAI, Anthropic, and Google.
Nimble ships as the `remote::nimble-search` provider, registered under the `builtin::websearch` toolgroup alongside the other web search backends. When a model calls the `web_search` tool, OGX routes the request to Nimble's [Search API](/nimble-sdk/web-tools/search) and returns ranked, grounded results with source URLs. No fork, no plugin.
The provider ships in **OGX v1.1.0 and later**.
### Web search for any model
Any model OGX serves can search the web through Nimble, with no model-specific code. A `web_search` tool call resolves to `builtin::websearch`, which dispatches to the registered `remote::nimble-search` provider automatically.
Nimble handles anti-bot evasion, JavaScript rendering, and geo-targeting underneath, so the model gets back clean, ranked results to ground its answer in.
### Quick Start
#### 1. Get a Nimble API key
Get your API key from [Nimble's dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available) and export it in the environment OGX runs in:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
#### 2. Register the provider and start OGX
Add Nimble to the `tool_runtime` section of your distribution config (for example, the `starter` distribution):
```yaml theme={"system"}
tool_runtime:
- provider_id: nimble-search
provider_type: remote::nimble-search
config:
api_key: ${env.NIMBLE_API_KEY:=}
max_results: 3
search_depth: lite
country: US
locale: en
```
Then start the server:
```bash theme={"system"}
ogx run starter
```
On boot, OGX auto-registers the toolgroup: `provider_id=nimble-search toolgroup_id=builtin::websearch`.
#### 3. Search the web from a model
Call the Responses API with the `web_search` tool enabled. OGX routes the call through `builtin::websearch` to Nimble:
```bash theme={"system"}
curl http://localhost:8321/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"input": "What were the biggest AI model releases this month? Cite your sources.",
"tools": [{"type": "web_search"}],
"stream": false
}'
```
The response contains a `web_search_call` item (status `completed`), followed by an assistant message grounded in live Nimble results with source URLs.
### Configuration
The `remote::nimble-search` provider accepts these keys under `config`:
**Required.** Your Nimble API key. Provide it once via the `NIMBLE_API_KEY` environment variable (referenced as `${env.NIMBLE_API_KEY:=}`), or per request via the `X-OGX-Provider-Data` header:
```json theme={"system"}
{"nimble_search_api_key": "your-api-key"}
```
A per-request header value overrides the configured key.
**Optional.** Maximum number of results to return. Defaults to `3`.
**Optional.** Controls the speed-vs-richness tradeoff. Defaults to `lite`.
* `lite`: titles, URLs, and snippets. Lowest latency, best for broad discovery.
* `deep`: full real-time page extraction for each result. Higher latency, richer content, and may require a higher Nimble tier. If your account lacks it, the provider returns a clear "tier not enabled" message rather than failing.
See [Search Depth](/nimble-sdk/web-tools/search-depth) for details.
**Optional.** ISO-2 country code for geo-targeted results (e.g. `US`, `GB`). Defaults to `US`. Can also be set per request via the model's location hint.
**Optional.** Language/locale code for results (e.g. `en`). Defaults to `en`.
### Using Nimble over MCP (alternative)
On an OGX release earlier than v1.1.0, or if you prefer an MCP-based setup, OGX can reach Nimble through its built-in Model Context Protocol support, with no provider configuration needed. Point a Responses `mcp` tool at Nimble's hosted MCP server:
```bash theme={"system"}
curl http://localhost:8321/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"input": "What are the latest changes to the EU AI Act? Search the web and cite sources.",
"tools": [{
"type": "mcp",
"server_label": "nimble",
"server_url": "https://mcp.nimbleway.com/mcp",
"authorization": "your-api-key"
}],
"stream": false
}'
```
Pass the raw API key in the tool's `authorization` field. OGX adds the `Bearer` prefix itself. The hosted server exposes the `nimble_search` tool alongside Nimble's other capabilities. See the [Nimble MCP server docs](/integrations/mcp-server/mcp-server) for the full tool list and authentication details.
### Additional Resources
The open agentic API server.
The Search endpoint behind the provider: parameters and response format.
Reach Nimble over the Model Context Protocol.
The `remote::nimble-search` reference in OGX's own documentation.
# Omnigent
Source: https://docs.nimbleway.com/integrations/connectors/omnigent
Omnigent ships three Nimble capabilities: a web_search provider, Extract Templates for structured scraping, and Agent API V2 for cited deep research.
## Overview
[Omnigent](https://github.com/omnigent-ai/omnigent) is an open-source AI agent framework and meta-harness from Databricks. It gives you one orchestration layer over Claude Code, Codex, Cursor, Pi, and agents you write yourself, with shared sessions, policies, and sandboxing.
Omnigent ships three separate Nimble capabilities. They are different products, not settings of one product.
| Capability | Omnigent name | What it does | Latency |
| - | - | - | - |
| [Search](/nimble-sdk/web-tools/search) | `web_search` with `search_provider: nimble` | Grounded live web results for any model | Seconds |
| [Extract Template](/nimble-sdk/web-tools/extract/template) | `nimble_extract` builtin | Runs one of your account's templates, returns structured JSON | Seconds |
| [Agent](/nimble-sdk/web-search-agents/overview) | `nimble_research` builtin | Autonomous research run with per-claim citations | Minutes |
Pick `web_search` to ground an answer, `nimble_extract` when you have a template for a known site shape, and `nimble_research` when the question needs many sources and an auditable answer.
## Prerequisites
The `nimble_extract` and `nimble_research` builtins first shipped in 0.9.0. Earlier releases carry only the `web_search` provider.
The only credential any of the three capabilities needs. Get one from the [dashboard](https://online.nimbleway.com/settings/api-keys). Every request Omnigent sends carries `X-Client-Source: omnigent`.
`nimble_research` calls Nimble through the `nimble-python` client, which ships in the optional `nimble` extra. `nimble_extract` and `web_search` call the API over HTTP and need no extra.
## Install
```bash theme={"system"}
# uv
uv pip install 'omnigent[nimble]'
# pip
pip install --pre 'omnigent[nimble]'
```
Two details in that command matter.
**Keep the quotes.** Shells such as zsh expand unquoted square brackets, so `pip install omnigent[nimble]` fails before pip runs.
**pip needs `--pre`.** Omnigent depends on `opentelemetry-instrumentation-fastapi`, which publishes only pre-release versions. pip skips pre-releases by default, finds nothing that satisfies the requirement, and gives up with `ResolutionImpossible`. `--pre` allows them. uv accepts a pre-release automatically when it is the only thing published, so it needs no flag.
Installing without the extra still succeeds. The Nimble client is imported lazily, so a base install fails on the first `nimble_research` call rather than at install time. `nimble_extract` and `web_search` keep working.
Confirm both builtins resolve from the installed release:
```bash theme={"system"}
python -c "from omnigent.tools.builtins import nimble_research, nimble_extract; print('ok')"
```
## Web search
Nimble is a first-class `web_search` provider. Set `search_provider: nimble` in any agent spec and the agent gets grounded, live web results from Nimble's [Search API](/nimble-sdk/web-tools/search). No fork, no plugin.
Omnigent lets you run any model and swap harnesses with a one-line change. Nimble brings that same flexibility to web search: one model-agnostic backend with anti-bot handling, JS rendering, and geo-targeting underneath. Databricks-hosted models get grounded web search out of the box.
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
A custom agent lives in its own directory with a `config.yaml` at the root. Create `research-agent/config.yaml`:
```yaml research-agent/config.yaml theme={"system"}
spec_version: 1
name: research-agent
prompt: |
You are a research assistant. Use web_search to ground your
answers in current information from the web.
executor:
type: omnigent
model: databricks-claude-sonnet-4-6 # run on any model
tools:
builtins:
- name: web_search
search_provider: nimble
api_key: ${NIMBLE_API_KEY}
```
```bash theme={"system"}
omni run ./research-agent -p "What were the biggest AI model releases this month?"
```
The agent now calls Nimble whenever it searches the web.
### Web search configuration
**Required.** Set to `nimble` to route web search through Nimble.
**Required.** Your Nimble API key. Use `${NIMBLE_API_KEY}` to read it from the environment instead of hardcoding it in the spec.
**Optional.** Number of results to return, from `1` to `100`. Defaults to `5`. Values outside the range are clamped.
**Optional.** Controls the speed-versus-richness tradeoff. Defaults to `lite`.
* `lite`: titles, URLs, and snippets. Lowest latency, best for broad discovery.
* `deep`: full real-time page extraction for each result. Higher latency, richer content.
See [Search Depth](/nimble-sdk/web-tools/search-depth) for details.
A fully configured provider looks like this:
```yaml theme={"system"}
tools:
builtins:
- name: web_search
search_provider: nimble
api_key: ${NIMBLE_API_KEY}
max_results: 10
search_depth: deep
```
## Extract Template
`nimble_extract` runs one of your account's [Extract Templates](/nimble-sdk/web-tools/extract/template) with a single synchronous call to `POST /v2/extract/templates/run`, and returns the template's structured, parsed results as JSON.
Templates are account-scoped. Each one declares its own input schema, and the model supplies a matching `params` object on every call.
```yaml research-agent/config.yaml theme={"system"}
tools:
builtins:
- name: nimble_extract
api_key: ${NIMBLE_API_KEY}
template: google_search # one of your account's templates
# optional:
# timeout_seconds: 120
```
The model then calls the tool with the template's parameters, for example `{"query": "nimbleway"}`.
### Find your templates
List the templates your account can reach. The response is paginated, with `total`, `limit`, and `offset` alongside `items`:
```bash theme={"system"}
curl -H "Authorization: Bearer $NIMBLE_API_KEY" \
'https://sdk.nimbleway.com/v2/extract/templates?limit=20'
```
Then fetch one by name to see the `params` it accepts. The input schema sits at `published_version.input_schema`:
```bash theme={"system"}
curl -H "Authorization: Bearer $NIMBLE_API_KEY" \
https://sdk.nimbleway.com/v2/extract/templates/google_search \
| jq '.published_version.input_schema'
```
Use the `required` and `properties` of that schema to see what the model must supply in `params`.
### Extract configuration
**Required.** Your Nimble API key.
**Required.** The name of one of your account's extract templates.
**Optional.** Read timeout for the template run. Defaults to `120`, clamped to `10` to `600`.
This is the migration target for the deprecated `/v1/agent` site-scraping path. The former `nimble_agent` tool name is retired rather than aliased, so it cannot silently resolve to a different API. See [Extract Template](/nimble-sdk/web-tools/extract/template).
## Deep research
`nimble_research` runs a research task on a [Nimble Agent](/nimble-sdk/web-search-agents/overview) through the Agents API. The builtin starts a run, polls it to a terminal status, then fetches the cited result. One call can take minutes.
```yaml research-agent/config.yaml theme={"system"}
tools:
builtins:
- name: nimble_research
api_key: ${NIMBLE_API_KEY}
timeout_seconds: 900 # set this explicitly, see below
# optional:
# agent_id: wsa_... # omit to let Nimble provision the agent
# poll_interval_seconds: 10
```
Set `timeout_seconds` in every spec. The default of `300` is often shorter than a default-effort run needs, so a spec left on defaults can time out on a perfectly healthy run. Raise it to suit the [effort level](/nimble-sdk/web-search-agents/efforts) you use.
### Which agent a run uses
`agent_id` selects the create route.
* **Omitted:** `POST /v2/agents/runs`. Nimble provisions an agent for the run and returns its ID. No agent has to exist first.
* **Provided:** `POST /v2/agents/{agent_id}/runs`, against an agent you own.
Either way the response carries both a run ID and a `web_search_agent_id`. Every later status and result request is addressed to the **returned** `web_search_agent_id`, never to the configured `agent_id`, because on the generic route the returned ID is the only one that exists. If the two are both present and disagree, the call fails rather than guessing which agent to address. The run was still created and billed, so the error carries the run ID for you to reconcile against.
### Per-call arguments
The spec configures credentials and timing. The model supplies the rest per call.
**Required.** The research task or question.
Optional. One of `low`, `medium`, `high`, or `x-high`. Omit it to use the agent or template default. The product default is [`high`](/nimble-sdk/web-search-agents/efforts), and template defaults vary.
Optional run mode. Exactly one of `research`, `enrichment`, or `dataset_building`.
Optional records to enrich: a JSON object, or an array of JSON objects, passed to the agent as input.
Optional JSON schema requesting structured output instead of prose.
Optional source guidance: `prioritize`, `avoid`, and `allow` or `block` groups of `{ title, domains, order? }`.
Optional hints forwarded to the run. `skill` names a skill identifier, `agent_name` names the run's agent.
`max` appears in the tool schema but is a [coming-soon](/nimble-sdk/web-search-agents/efforts) custom-budget capability, not a selectable tier. Supplying it stops the call before the run is created, with product-team contact guidance. It is never silently substituted for another effort level.
### Spec configuration
**Required.** Your Nimble API key.
Optional. A Nimble Agent you own, in the form `wsa_...`. Omit it to have Nimble provision one per run.
Optional overall deadline. Defaults to `300`, clamped to `10` to `3600`. Raise it: the default is shorter than a typical run.
Optional interval between status polls. Defaults to `10`, clamped to `0.5` to `30`.
### Run an agent
```bash theme={"system"}
omni run ./research-agent -p "Research the EU AI Act enforcement timeline and cite your sources."
```
The model calls `nimble_research`, the builtin drives the run to completion, and the agent answers from a source-grounded result.
### What the tool returns
A bounded JSON envelope:
```json theme={"system"}
{
"run_id": "task_run_",
"web_search_agent_id": "wsa_...",
"status": "completed",
"output": {
"type": "text",
"content": "..."
},
"trust": {
"confidence": "...",
"reasoning": "...",
"sources": [{ "url": "...", "type": "...", "title": "..." }],
"claims": [{ "claim": "...", "confidence": "...", "citations": ["..."] }]
}
}
```
`output.type` is `text` for prose or `json` when you passed an `output_schema`. Run IDs have the form `task_run_`.
Run statuses are `queued` and `running` while work continues, and `completed`, `failed`, or `cancelled` once terminal.
The envelope is capped so a large result cannot exhaust the model's context: 50,000 characters of content, 10 sources, 10 claims, and 3 citations per claim. Truncation is reported rather than hidden.
### Billing and retries
Run creation is billable and not idempotent, and the API exposes no idempotency key. The builtin therefore issues the create call **exactly once and never retries it**, on any outcome. Retries are confined to the read-only calls: polling retries transport errors, 408, 429, and 5xx, and the result call re-checks only the documented 409.
That splits create failures into two classes.
| Failure | Billing outcome | What to do |
| - | - | - |
| Transport error, timeout, 408, 5xx | **Unknown.** The request may have reached Nimble. | Do not resubmit. Reconcile against the run ID, or your account's run history when no ID survived. |
| 401, 403, 404, 422, create-time 429 | **Nothing was created.** A rate limiter refuses the request before a run starts. | Safe to retry. |
Once creation succeeds the run exists and is billed, so every later failure carries the same do-not-resubmit guidance, keyed to the run ID.
Event streaming and interaction chaining are not exposed by this builtin. Use the [Agent API](/nimble-sdk/web-search-agents/overview) directly if you need them.
## Additional Resources
The open-source agent framework and meta-harness.
Define and run a custom agent: directory layout, `config.yaml`, and the run command.
The Search endpoint behind the `web_search` provider.
Browse, run, and generate templates for popular websites.
The deep-research capability behind `nimble_research`.
The full Databricks integration: Marketplace MCP, Genie, and SQL-native enrichment.
# OmniRoute
Source: https://docs.nimbleway.com/integrations/connectors/omniroute
Nimble is a built-in provider in OmniRoute: one API key serves both web search and page fetching.
### Overview
[OmniRoute](https://github.com/diegosouzapw/OmniRoute) is an open-source AI gateway that puts hundreds of model and tool providers behind one local endpoint. It runs on your own machine, stores provider credentials, and routes each request with automatic fallback. For web search and fetch, fallback applies only when a request leaves out `provider`. Setting it to `nimble-search` keeps the request on Nimble.
Nimble ships as the `nimble-search` provider, built into OmniRoute itself. It is registered once and serves two surfaces:
* **Web search** through `POST /v1/search`, backed by Nimble's [Search API](/nimble-sdk/web-tools/search).
* **Page fetching** through `POST /v1/web/fetch`, backed by [Extract](/nimble-sdk/web-tools/extract/quickstart).
One Nimble API key covers both.
The provider ships in **OmniRoute v3.8.51 and later**.
### What this integration covers
Search and Extract only. Map, Crawl, and the Web Search Agent are not reachable through OmniRoute today. For those, call the [Nimble SDK](/nimble-sdk/getting-started/overview) directly or connect the [Nimble MCP server](/integrations/mcp-server/mcp-server).
### Quick Start
#### 1. Get a Nimble API key
Get your API key from [Nimble's dashboard](https://online.nimbleway.com/settings/api-keys). A free trial is available.
#### 2. Install and start OmniRoute
OmniRoute needs Node.js 22 (22.22.2 or later) or Node.js 24. Node.js 23 is not supported. Check the `engines` field in [OmniRoute's package.json](https://github.com/diegosouzapw/OmniRoute/blob/main/package.json) for the exact range.
```bash theme={"system"}
npm install -g "omniroute@>=3.8.51"
omniroute
```
The dashboard runs at `http://localhost:20128`. The API is served at `http://localhost:20128/v1`.
#### 3. Add the Nimble credential
In the dashboard, go to **Providers**, find **Nimble Search**, and paste your Nimble API key. Test the connection from the dashboard to check the key.
#### 4. Search the web
Requests to OmniRoute use your OmniRoute API key, not your Nimble key. Create or copy it from **Dashboard → API Keys**. The gateway holds the Nimble credential server-side, so callers never see it.
Select the provider by id in the request body:
```bash theme={"system"}
curl http://localhost:20128/v1/search \
-H "Authorization: Bearer YOUR_OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "latest AI developments",
"provider": "nimble-search",
"search_type": "web",
"max_results": 5
}'
```
#### 5. Fetch a page
The same provider id selects Nimble for extraction:
```bash theme={"system"}
curl http://localhost:20128/v1/web/fetch \
-H "Authorization: Bearer YOUR_OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"provider": "nimble-search",
"format": "markdown",
"include_metadata": true
}'
```
The response returns the page content in the requested format, the list of links found on the page, and the title and description when `include_metadata` is set.
### Configuration
Use `nimble-search` in the `provider` field of both `/v1/search` and `/v1/web/fetch`. The dashboard also shows a short alias, `nimble`, but the API doesn't accept it.
| OmniRoute parameter | Behavior with Nimble |
| - | - |
| `search_type` | `web` and `news` are supported. `news` maps to Nimble's news focus. The `x` type (X posts) is not supported. |
| `max_results` | Defaults to `5`. The provider caps it at `100`, which is the Search API maximum. |
| `country` | Sent as an uppercase two-letter country code (e.g. `US`). |
| `language` | Sent as Nimble's `locale`. |
| `time_range` | `hour`, `day`, `week`, `month`, and `year` pass through. `any` is treated as no filter. |
| `filters.include_domains` / `filters.exclude_domains` | Mapped onto Nimble's own domain filters. |
Searches run at `lite` depth and return plain text. Deeper extraction per result is not exposed through the gateway. For that, call [Search](/nimble-sdk/web-tools/search) directly and set `search_depth`.
`format` accepts `markdown` (the default), `html`, `links`, and `screenshot`.
A screenshot comes back as a base64 PNG data URL. The list of page links is always returned alongside the content, whatever format you asked for. Rendering is set to `auto`, so Nimble decides per domain whether a browser is needed.
Nimble gets 10 seconds per search and 30 seconds per fetch. OmniRoute's overall search budget is 15 seconds.
If a request leaves out `provider`, OmniRoute can retry a failed call with another configured provider. Set `provider` to `nimble-search` to keep the request on Nimble and see Nimble's errors directly.
### Additional Resources
The `nimble-search` entry in OmniRoute's own documentation.
The open-source AI gateway.
Focus modes, search depth, and filtering explained.
The extraction endpoint behind the fetch surface.
# OpenAI
Source: https://docs.nimbleway.com/integrations/connectors/openai
Use the Nimble SDK as a tool inside OpenAI function calling and the Agents SDK.
Give OpenAI models real-time web data by wiring the Nimble SDK into function calling. No extra packages needed.
## Prerequisites
```bash Python theme={"system"}
pip install openai nimble_python
```
```bash Node theme={"system"}
npm install openai @nimble-way/nimble-js
```
Set environment variables:
```bash theme={"system"}
export OPENAI_API_KEY="your-openai-api-key"
export NIMBLE_API_KEY="your-nimble-api-key"
```
Get a Nimble API key from the [dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available).
The examples set the Nimble client's `client_source` (`clientSource` in Node) to `openai`, which sends an `X-Client-Source` header that attributes requests to this integration. It defaults to `sdk` when unset.
## Quick Start: OpenAI Agents SDK
The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) provides a higher-level framework for building agents. Wrap Nimble tools with the `@function_tool` decorator and the SDK handles the tool-calling loop automatically.
```bash theme={"system"}
pip install openai-agents nimble_python
```
```python Python theme={"system"}
import os
import asyncio
from agents import Agent, Runner, function_tool
from nimble_python import Nimble
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="openai")
@function_tool
def nimble_search(query: str) -> str:
"""Search the web using Nimble and return relevant results."""
result = nimble_client.search(query=query)
return str(result)
@function_tool
def nimble_extract(url: str) -> str:
"""Extract clean content from a URL using Nimble."""
result = nimble_client.extract(url=url)
return str(result)
async def main():
agent = Agent(
name="Web Research Agent",
instructions=(
"You are a research assistant with access to real-time web data. "
"Use nimble_search to find information and nimble_extract to read specific pages."
),
tools=[nimble_search, nimble_extract],
)
result = await Runner.run(agent, "What are the latest trends in AI agents?")
print(result.final_output)
asyncio.run(main())
```
## Agent API V2: autonomous research tool
The tools above (`search`, `extract`, `crawl`, `map`, and [Extract Templates](/nimble-sdk/web-tools/extract/template)) are **synchronous and single-shot**. One call returns data directly, and the OpenAI model does the reasoning.
[Agent API V2](/nimble-sdk/web-search-agents/overview) is different. It exposes an **asynchronous research agent** that plans, searches across many sources, and returns a synthesized answer with a per-claim [trust report](/nimble-sdk/web-search-agents/trust). The lifecycle is **create a run → poll to a terminal state → retrieve the result**. Wrap that whole lifecycle in one function so the OpenAI model sees a single "deep research" tool.
### 1. Wrap the run lifecycle
This helper uses a stable `agent_name`, so every call — from this process or any other — reuses the same agent and its memory instead of spinning up a fresh one each time. It creates the run, polls until terminal, handles `failed` and `cancelled` runs, and returns the answer plus its trust and citation metadata. Errors return a safe message, so no API key or raw exception is surfaced.
```python Python theme={"system"}
import os
import time
from nimble_python import Nimble
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="openai")
# Stable name for this integration's research agent. The first call creates
# it; every later call (here or from another process) reuses it and its memory.
# Pick a name unique to this deployment - anyone reusing the same name on the
# same account reuses the same agent and its memory.
NIMBLE_AGENT_NAME = os.environ["NIMBLE_AGENT_NAME"]
def run_deep_research(query: str, timeout_s: int = 300) -> dict:
"""Run one Agent API V2 research task end to end and return a cited answer."""
try:
# Create-or-reuse the named agent, then start the run.
run = nimble_client.agents.run(input=query, agent_name=NIMBLE_AGENT_NAME)
agent_id = run.web_search_agent_id
# Poll until the run reaches a terminal state.
deadline = time.time() + timeout_s
while run.is_active:
if time.time() > deadline:
return {"status": "timeout", "error": "Run did not finish in time."}
time.sleep(10)
run = nimble_client.agents.runs.get(run.id, agent_id=agent_id)
# Handle failed or cancelled runs.
if run.status != "completed":
return {"status": run.status, "error": f"Run ended as '{run.status}'."}
# Retrieve the completed result and summarize its trust report.
result = nimble_client.agents.runs.result(run.id, agent_id=agent_id)
output = result.output
return {
"status": "completed",
"answer": output.content,
"confidence": output.trust.confidence,
"citations": [
{"url": s.url, "title": s.title, "source_type": s.type}
for s in output.trust.sources
],
}
except Exception:
# Never leak credentials or raw exceptions back to the model.
return {"status": "error", "error": "Nimble request failed."}
```
Pass an `output_schema` to `agents.run` (or `agents.runs.create`) to get structured JSON in `result.output` instead of prose. Trust is then keyed by JSON path. See [Dataset Building](/nimble-sdk/web-search-agents/use-cases/dataset-building).
### 2. Expose it as a tool
Wrap the helper with `@function_tool` and hand it to an agent. The model calls one tool; the async run loop stays hidden.
```python Python theme={"system"}
import json
import asyncio
from agents import Agent, Runner, function_tool
@function_tool
def nimble_deep_research(query: str) -> str:
"""Run an autonomous, fully-cited web-research task with Nimble Agent API V2.
Use for open-ended questions that need synthesis across multiple sources,
not single-page lookups. Returns a cited answer with a trust grade.
"""
return json.dumps(run_deep_research(query), default=str)
async def main():
agent = Agent(
name="Deep Research Agent",
instructions=(
"You are a research analyst. Use nimble_deep_research for questions "
"that need current, cited web evidence. Report the confidence grade "
"and cite the sources it returns."
),
tools=[nimble_deep_research],
)
result = await Runner.run(
agent,
"Compare the pricing and positioning of Datadog and Grafana Cloud.",
)
print(result.final_output)
asyncio.run(main())
```
The tool returns the synthesized answer, an overall `confidence` grade (`high`, `medium`, or `low`), and the source URLs behind it, so the OpenAI model can weigh how much to trust each claim. See [Trust](/nimble-sdk/web-search-agents/trust) for the full report structure.
## Chat Completions API
For more control over the tool-calling loop, define Nimble tools as OpenAI function schemas and handle calls manually.
### 1. Define the Tool Schema
```python Python theme={"system"}
tools = [
{
"type": "function",
"function": {
"name": "nimble_search",
"description": "Search the web using Nimble and return relevant results.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to execute"
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "nimble_extract",
"description": "Extract clean content from a URL using Nimble.",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to extract content from"
}
},
"required": ["url"]
}
}
}
]
```
### 2. Handle Tool Calls
```python Python theme={"system"}
import os
import json
from openai import OpenAI
from nimble_python import Nimble
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
nimble_client = Nimble(api_key=os.environ["NIMBLE_API_KEY"], client_source="openai")
def handle_tool_call(name, args):
if name == "nimble_search":
result = nimble_client.search(query=args["query"])
return json.dumps(result, default=str)
elif name == "nimble_extract":
result = nimble_client.extract(url=args["url"])
return json.dumps(result, default=str)
messages = [
{"role": "system", "content": "You are a research assistant with access to real-time web data."},
{"role": "user", "content": "What are the latest trends in AI agents?"}
]
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
if assistant_msg.tool_calls:
for tc in assistant_msg.tool_calls:
args = json.loads(tc.function.arguments)
result = handle_tool_call(tc.function.name, args)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
final = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
print(final.choices[0].message.content)
```
### Node.js Example
```typescript Node theme={"system"}
import OpenAI from "openai";
import Nimble from "@nimble-way/nimble-js";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY, clientSource: "openai" });
const tools: OpenAI.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "nimble_search",
description: "Search the web using Nimble and return relevant results.",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "The search query to execute" },
},
required: ["query"],
},
},
},
{
type: "function",
function: {
name: "nimble_extract",
description: "Extract clean content from a URL using Nimble.",
parameters: {
type: "object",
properties: {
url: { type: "string", description: "The URL to extract content from" },
},
required: ["url"],
},
},
},
];
async function handleToolCall(name: string, args: Record) {
if (name === "nimble_search") {
return await nimble.search({ query: args.query });
}
if (name === "nimble_extract") {
return await nimble.extract({ url: args.url });
}
}
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a research assistant with access to real-time web data." },
{ role: "user", content: "What are the latest trends in AI agents?" },
];
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages,
tools,
});
const assistantMsg = response.choices[0].message;
messages.push(assistantMsg);
if (assistantMsg.tool_calls) {
for (const tc of assistantMsg.tool_calls) {
const args = JSON.parse(tc.function.arguments);
const result = await handleToolCall(tc.function.name, args);
messages.push({
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify(result),
});
}
const final = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
console.log(final.choices[0].message.content);
}
```
## Available Tools
Any Nimble SDK method can be exposed as an OpenAI tool. Here are the most common ones:
| Tool | SDK Method | Use Case |
| - | - | - |
| `nimble_search` | `client.search()` | Web search with structured results |
| `nimble_extract` | `client.extract()` | Extract content from a URL |
| `nimble_crawl` | `client.crawl.run()` | Crawl an entire site |
| `nimble_map` | `client.map()` | Discover all URLs on a domain |
See the [Python SDK](/nimble-sdk/sdks/python) and [Node SDK](/nimble-sdk/sdks/node) docs for the full list of methods and parameters.
## Next Steps
Full Python SDK reference with all methods and configuration options
Full Node.js SDK reference with TypeScript support
Agent API V2: autonomous research runs with per-claim trust
Pre-built LangChain tools and retrievers for Nimble
# Smithery
Source: https://docs.nimbleway.com/integrations/connectors/smithery
Connect any AI agent to Nimble web tools via the Smithery MCP registry
Give AI agents real-time web data by connecting to Nimble through the [Smithery](https://smithery.ai) MCP registry — no manual server configuration needed.
Smithery is an MCP registry that hosts and manages MCP server connections. The `nimble/nimble-mcp` package exposes all Nimble tools — search, extract, crawl, map, and agents — to any MCP-compatible client.
## Prerequisites
```bash theme={"system"}
npm install -g @smithery/cli@latest
```
Set environment variables:
```bash theme={"system"}
export NIMBLE_API_KEY="your-nimble-api-key"
```
Get a Nimble API key from the [dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available).
## Quick Start — CLI
Connect to the Nimble MCP server using the Smithery CLI in three steps.
```bash theme={"system"}
smithery namespace create {your-namespace}
```
```bash theme={"system"}
smithery mcp add nimble/nimble-mcp --headers '{"Authorization":"Bearer your-api-key"}'
```
List available tools and call them directly from the CLI:
```bash theme={"system"}
smithery tool list {connection}
smithery tool call {connection} {tool_name} '{"key": "value"}'
```
## Programmatic Usage
Use the `@smithery/api` package to connect programmatically and call Nimble tools from any TypeScript application or AI framework.
### Install
```bash theme={"system"}
npm install @smithery/api @ai-sdk/mcp @ai-sdk/anthropic ai
```
```bash theme={"system"}
npm install @smithery/api @modelcontextprotocol/sdk
```
### Connect and Call Tools
```typescript theme={"system"}
import Smithery from "@smithery/api"
import { createMCPClient } from "@ai-sdk/mcp"
import { generateText } from "ai"
import { anthropic } from "@ai-sdk/anthropic"
import { createConnection } from "@smithery/api/mcp"
const smithery = new Smithery()
// Create a connection with server config
const conn = await smithery.connections.create(
"{your-namespace}",
{
mcpUrl: "https://nimble.run.tools",
headers: {
// Required. Store the raw key; the Bearer prefix belongs here, not in the value.
"Authorization": "Bearer your-api-key"
},
}
)
// Connect using the returned connection ID
const { transport } = await createConnection({
client: smithery,
namespace: "{your-namespace}",
connectionId: conn.connectionId,
})
const mcpClient = await createMCPClient({ transport })
const tools = await mcpClient.tools()
const { text } = await generateText({
model: anthropic("claude-sonnet-4-20250514"),
tools,
prompt: "Use the available tools to help me.",
})
await mcpClient.close()
```
```typescript theme={"system"}
import Smithery from "@smithery/api"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { createConnection } from "@smithery/api/mcp"
const smithery = new Smithery()
// Create a connection with server config
const conn = await smithery.connections.create(
"{your-namespace}",
{
mcpUrl: "https://nimble.run.tools",
headers: {
// Required. Store the raw key; the Bearer prefix belongs here, not in the value.
"Authorization": "Bearer your-api-key"
},
}
)
// Connect using the returned connection ID
const { transport } = await createConnection({
client: smithery,
namespace: "{your-namespace}",
connectionId: conn.connectionId,
})
const mcpClient = new Client(
{ name: "my-app", version: "1.0.0" },
{ capabilities: {} }
)
await mcpClient.connect(transport)
const { tools } = await mcpClient.listTools()
const result = await mcpClient.callTool({
name: "tool_name",
arguments: { key: "value" }
})
```
## Available Tools
Smithery auto-discovers all tools from the Nimble MCP server:
| Tool | Description |
| - | - |
| `search` | Web search with configurable depth and focus modes |
| `extract` | Extract content from any URL with rendering support |
| `map` | Discover all URLs on a website via sitemaps and link crawling |
| `crawl` | Crawl multiple pages with path filtering and progress tracking |
| `agents` | Run pre-built Extract Templates for structured data |
See the [Nimble MCP Server](/integrations/mcp-server/mcp-server) docs for the
full list of available tools and configuration options.
## Next Steps
Full MCP server setup for Cursor, Claude Desktop, and other clients
Web search with depth levels, filtering, and AI answers
Use Nimble with Google ADK agents via MCP
Use Nimble with OpenAI function calling and the Agents SDK
# Vercel AI SDK
Source: https://docs.nimbleway.com/integrations/connectors/vercel-ai-sdk
Integrate Nimble with the Vercel AI SDK to give your AI agents real-time web search, page extraction, and cited deep research.
## Overview
The [`@nimble-way/ai-sdk`](https://www.npmjs.com/package/@nimble-way/ai-sdk) package provides pre-built tools for Vercel's AI SDK v6. Register them on an agent and the model decides when to search the web, read a page, or commission deeper research. Nimble runs the request and returns clean, structured results to cite.
* **Five tools, zero boilerplate**: search, extract, and the three deep-research run tools.
* **Works with any model**: OpenAI, Anthropic, Google, and others supported by the AI SDK.
* **Two search depths**: `lite` for fast metadata, `deep` for full page content.
* **Asynchronous deep research**: start a Nimble Agent run, collect the cited answer minutes later.
* **Type-safe**: written in TypeScript with typed options and output.
These are app-side AI SDK `tool()` definitions, not provider-executed search. They behave the same whether the model call routes through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) or a provider SDK directly. The gateway, when present, routes only the model call.
Ships **Search**, **Extract**, and **Nimble Agent** runs. Map and Crawl are planned as follow-ups.
Building on [eve](https://eve.dev), Vercel's agent framework? Use the [Vercel eve connector](/integrations/connectors/vercel-eve) instead. It mounts the same capabilities as an eve extension and can replace eve's built-in `web_search` and `web_fetch`.
## Prerequisites
The package targets the Node.js runtime. Edge and serverless runtimes are expected to work but are not yet verified, so prefer the Node runtime.
`ai` (v6) and `zod` (`^3.25.76` or `^4.1.8`) are peer dependencies. Your app supplies both.
Required by every tool. Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys). Keep it server-side: the key never appears in model inputs or tool outputs.
Required by the three agent run tools. A Nimble Agent, created once in the dashboard or through `POST /v2/agents`. The ID looks like `wsa_01j9x8...`. The search and extract tools do not need it.
## Quick Start
```bash theme={"system"}
npm install @nimble-way/ai-sdk ai @ai-sdk/openai
```
`ai` (v6) and `zod` are peer dependencies. The examples use OpenAI via [`@ai-sdk/openai`](https://www.npmjs.com/package/@ai-sdk/openai), but `nimbleSearch` works with any AI SDK model provider.
Get a Nimble key from the [dashboard](https://online.nimbleway.com/settings/api-keys) (free trial available), then set both keys:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
export OPENAI_API_KEY="your-openai-api-key"
```
You can also pass the Nimble key inline: `nimbleSearch({ apiKey: '...' })`.
For [deep research](#deep-research-with-nimble-agents), also set `NIMBLE_AGENT_ID`.
```ts theme={"system"}
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { nimbleSearch } from '@nimble-way/ai-sdk';
const { text } = await generateText({
model: openai('gpt-5'),
prompt: 'What are the latest developments in agentic web search? Cite sources.',
tools: {
webSearch: nimbleSearch({ searchDepth: 'lite', maxResults: 5 }),
},
stopWhen: stepCountIs(3),
});
console.log(text);
```
## How it works
`nimbleSearch()` registers a `webSearch` tool the model can call when it needs current information.
When the prompt needs live data, the model emits a tool call with a `query` (and optional `maxResults`).
The query goes to Nimble's Web Search API, which returns clean, structured results.
Results are fed back to the model, which uses them to write a grounded, citable answer. `stopWhen: stepCountIs(n)` caps how many search rounds a single turn can take.
Use `stepCountIs`, not `isStepCount`. The latter does not exist in `ai` v6. Set it on every agent to prevent runaway loops and unbounded cost: `3`–`5` for chat, higher for autonomous agents.
## Search
`nimbleSearch()` grounds an answer in live web results. Configure it once; the model only ever supplies `{ query, maxResults? }`.
### Next.js route handler
For a streaming chat app, swap `generateText` for `streamText` inside a route handler and return `toUIMessageStreamResponse()`. The client connects with the AI SDK [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) hook, with no extra wiring needed.
```ts theme={"system"}
// app/api/chat/route.ts
import {
convertToModelMessages,
streamText,
stepCountIs,
type UIMessage,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { nimbleSearch } from '@nimble-way/ai-sdk';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai('gpt-5'),
messages: await convertToModelMessages(messages),
tools: {
webSearch: nimbleSearch({ searchDepth: 'lite', maxResults: 5 }),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
```
### Search options
Nimble API credentials. Defaults to `process.env.NIMBLE_API_KEY`.
`'lite'` returns metadata only (fast); `'deep'` returns full page content. Default `'lite'`.
Default number of results per search. Type `number`, default `5`.
Hard upper limit on results the model can request. Type `number`, default `10`.
Per-result content truncation, in characters. Type `number`, default `10_000`.
Two-letter country code for localization. Type `string`, default `'US'`.
Language preference. Type `string`, default `'en'`.
Injectable `NimbleSearchClient` for testing. Optional.
### Search response
Each tool call returns a structured result the model can reason over:
```ts theme={"system"}
{
query: string;
requestId?: string;
totalResults?: number;
results: Array<{
title: string;
url: string;
description?: string;
content?: string; // deep searches only
position?: number;
entityType?: string;
}>;
}
```
## Extract
`nimbleExtract()` registers an `extract` tool that takes a single URL and returns clean page content, markdown by default, for the model to read, quote, or summarize. The model only ever supplies `{ url }`; all policy below is developer-controlled.
```ts theme={"system"}
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { nimbleExtract } from '@nimble-way/ai-sdk';
const { text } = await generateText({
model: openai('gpt-5'),
prompt: 'Summarize https://en.wikipedia.org/wiki/Web_scraping',
tools: { extract: nimbleExtract({ format: 'markdown' }) },
// Allow a step after the tool call so the model can read the page.
stopWhen: stepCountIs(2),
});
console.log(text);
```
Register both tools together so the model can search, then read the best result:
```ts theme={"system"}
tools: {
webSearch: nimbleSearch(),
extract: nimbleExtract(),
}
```
### Extract options
Configure `nimbleExtract()` once; the model only ever supplies `{ url }`.
Nimble API credentials. Defaults to `process.env.NIMBLE_API_KEY`.
Content format returned to the model: `'markdown'` or `'html'`. Default `'markdown'`.
Two-letter ISO country code for geolocation / proxy. Type `string`, optional.
Extracted content truncation, in characters. Type `number`, default `50_000`.
Injectable `NimbleExtractClient` for testing. Optional.
### Extract response
```ts theme={"system"}
{
url: string;
status: string; // e.g. 'success'
statusCode?: number;
format: 'markdown' | 'html';
content: string; // truncated to maxContentLength
links?: string[];
}
```
## Deep research with Nimble Agents
`nimbleSearch()` and `nimbleExtract()` are synchronous building blocks: one call, one response, seconds. A Nimble Agent run is a different capability. An autonomous agent plans, searches, reads, and cross-checks many sources, then returns a final answer with per-claim citations and confidence. It takes minutes, not seconds.
| | Search / Extract | Nimble Agent run |
| - | - | - |
| Latency | Seconds | Minutes, depending on effort |
| Shape | Request, then response | Start, check status, fetch result |
| Output | Ranked results or page content | Final answer plus sources, citations, confidence |
| Best for | Grounding a chat turn | Briefs, due diligence, monitoring, enrichment |
Because a run outlives any sensible HTTP request, the package splits the lifecycle across three tools. By default, no chat request is held open for the research duration.
Starts a run. Returns the run ID immediately, without waiting for the research.
Reports the current status. Instant, and never waits.
Fetches the answer once the run completes.
### Start now, answer later
Point `NIMBLE_AGENT_ID` at your agent instance, then start the run and persist the returned `runId`.
```ts theme={"system"}
// Request 1: start the run. Returns without waiting for the research.
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { nimbleAgentStartRun } from '@nimble-way/ai-sdk';
import type { NimbleAgentStartRunOutput } from '@nimble-way/ai-sdk';
const { text, steps } = await generateText({
model: openai('gpt-5'),
prompt:
'Start deep research on the EU AI Act enforcement timeline, then tell ' +
'the user research is underway and the answer takes a few minutes.',
tools: { startResearch: nimbleAgentStartRun({ effort: 'medium' }) },
stopWhen: stepCountIs(2),
});
// Pull the run ID out of the tool result and persist it.
const started = steps
.flatMap((step) => step.toolResults)
.find((result) => result.toolName === 'startResearch')?.output as
| NimbleAgentStartRunOutput
| undefined;
if (started) {
// Persist the run ID however your app stores state: database,
// session, or job queue. `saveRunId` here is your own function.
await saveRunId(started.runId); // e.g. 'task_run_8f3c...'
}
```
The start tool returns the handle you need to resume:
```ts theme={"system"}
{
runId: 'task_run_8f3c...', // pass this to the status and result tools
agentId: 'wsa_01j9x8...',
interactionId: 'int_...',
status: 'queued',
effort: 'medium',
createdAt: '2026-07-31T10:00:00Z',
}
```
Preserve `runId` outside the request. Write it to your database, session, or job queue. Research runs take minutes, so a run started in one request is almost never finished in that same request. Holding an ordinary chat request open until completion times out the request and wastes the async design.
Minutes later, in a different request, server, or process, only the `runId` crosses over.
```ts theme={"system"}
// Request 2: minutes later, a different request or process.
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { nimbleAgentRunResult } from '@nimble-way/ai-sdk';
import type { NimbleAgentRunResultOutput } from '@nimble-way/ai-sdk';
const { text, steps } = await generateText({
model: openai('gpt-5'),
prompt:
`The user is back. Fetch research run ${runId}. If it is not ready, say ` +
`so briefly. If it is, answer from it and keep the citation callouts.`,
tools: { getResearchResult: nimbleAgentRunResult() },
stopWhen: stepCountIs(2),
});
const result = steps
.flatMap((step) => step.toolResults)
.find((r) => r.toolName === 'getResearchResult')?.output as
| NimbleAgentRunResultOutput
| undefined;
if (result?.ready) {
const { confidence, sources, claims } = result.output.trust;
console.log(`${confidence} confidence, ${sources.length} sources, ${claims.length} claims`);
}
```
Register all three tools together when the model should also be able to check progress:
```ts theme={"system"}
tools: {
startResearch: nimbleAgentStartRun({ effort: 'medium' }),
checkResearch: nimbleAgentRunStatus(),
getResearchResult: nimbleAgentRunResult(),
}
```
### Runs that are still working
A run that has not finished is a normal state, not an error. `nimbleAgentRunResult()` returns `ready: false` so the model can tell the user to check back.
```ts theme={"system"}
{
ready: false,
runId: 'task_run_8f3c...',
agentId: 'wsa_01j9x8...',
status: 'running', // or 'queued'
isActive: true,
effort: 'medium',
createdAt: '2026-07-31T10:00:00Z',
startedAt: '2026-07-31T10:00:01Z',
}
```
Use `nimbleAgentRunStatus()` for cheap progress checks that never fetch the result. Its output adds `isActive`, `startedAt`, `completedAt`, and `error` on failed runs.
By default the result tool never blocks. Behind a queue worker, rather than a chat route, you can let a single call ride out a short remainder:
```ts theme={"system"}
nimbleAgentRunResult({ wait: { timeoutMs: 120_000, pollIntervalMs: 2_000 } })
```
Pass `wait: true` for the defaults: a `300_000` ms timeout and a `2_000` ms poll interval, with a `100` ms floor on the interval. On timeout the tool returns `ready: false` and the run stays healthy. The package never polls without a bound.
Waiting honors the AI SDK's per-call `AbortSignal`. Aborting stops the wait only. The run keeps going on Nimble's side and stays resumable from the same `runId`.
A terminally failed or cancelled run throws a typed `NimbleAgentRunError`. Its `reason` is `'failed'`, `'cancelled'`, `'protocol'`, or `'request'`, and it always carries `runId` so your code can still reference the run. An HTTP status, when there is one, is on `error.status`.
```ts theme={"system"}
import { NimbleAgentRunError } from '@nimble-way/ai-sdk';
try {
// ...
} catch (error) {
if (error instanceof NimbleAgentRunError) {
console.error(error.reason, error.runId, error.status);
}
throw error;
}
```
A still-active run and a wait timeout are not errors.
### Results and citations
A completed run returns prose or structured data, along with `trust` metadata passed through verbatim from the API so citation markers stay aligned with the answer.
```ts theme={"system"}
{
ready: true;
runId: string;
agentId: string;
status: 'completed';
effort: 'low' | 'medium' | 'high' | 'x-high' | 'max';
createdAt: string;
startedAt?: string;
completedAt?: string;
output:
| { type: 'text'; text: string; trust: NimbleAgentTrust }
| { type: 'json'; json: object | unknown[]; trust: NimbleAgentTrust };
}
```
`trust` carries the sources consulted, the per-claim citations, and confidence:
```ts theme={"system"}
{
confidence: 'high' | 'medium' | 'low' | 'pre_existing';
reasoning: string;
sources: Array<{
url: string;
type: 'primary' | 'secondary';
title?: string;
source_category?: 'official' | 'news' | 'social' | 'academic' | 'aggregator' | 'other';
}>;
claims: Array<{
callout?: number; // text answers: the [1]-style marker in the prose
path?: string; // json answers: the JSON path of the value
confidence: 'high' | 'medium' | 'low' | 'pre_existing';
reasoning: string;
citations: Array<{ url: string; title?: string; excerpts?: string[] }>;
}>;
}
```
Text answers key each claim by `callout`, matching the numeric markers in the prose. Structured answers key each claim by `path`, the JSON path of the value. Exactly one of the two is present. See [Trust and citations](/nimble-sdk/web-search-agents/trust) for how confidence is graded.
### Agent run options
All three factories share this configuration. Every field is optional.
The Nimble Agent to run, in the form `wsa_...`. Defaults to `process.env.NIMBLE_AGENT_ID`. Resolved when the tool executes, so the model can never choose the agent.
Nimble API credentials. Defaults to `process.env.NIMBLE_API_KEY`.
Injectable `NimbleAgentRunsClient` for testing. Optional.
Passthrough for `baseURL`, `fetch`, `timeout`, and `maxRetries` on the Nimble client. Optional.
`nimbleAgentStartRun()` adds two more:
Effort used when the model does not choose one: `'low'`, `'medium'`, `'high'`, `'x-high'`, or `'max'`. Leave it unset to use the agent instance's own default. Higher tiers research more sources and take longer. See [Efforts](/nimble-sdk/web-search-agents/efforts).
Upper bound on the effort the model may request. Model choices above the cap are clamped down to it. Default `'high'`, so a model cannot trigger the `x-high` or `max` cost tiers on its own. It never limits the developer-set `effort`.
`nimbleAgentRunResult()` adds `wait`, documented under [bounded waiting](#runs-that-are-still-working).
The model-facing inputs stay small: `{ task, effort? }` for the start tool and `{ runId }` for the status and result tools. The agent identity, credentials, and wait policy are never model-controlled.
## Limitations
* **Search, Extract, and Nimble Agent runs** ship today. Map and Crawl are planned follow-ups.
* **Agent runs need a pre-created agent instance.** Set `NIMBLE_AGENT_ID`. Creating and managing agents, and managing templates, are deliberately not model-callable tools in this release.
* **Run event streaming (SSE) is not exposed** by this release. Use the status and result tools.
* **No built-in answer generation in Search.** The tool returns results and the model writes the answer.
* **`searchDepth: 'fast'` is not available** in this package.
* **Node.js runtime** (18 or later) is the supported target. Edge and serverless compatibility is unverified.
## Resources
`@nimble-way/ai-sdk` on npm.
Source, README, and issues.
Nimble's underlying search capability.
Nimble's underlying page extraction capability.
The deep-research capability behind the run tools.
Runnable integration examples.
# Vercel eve
Source: https://docs.nimbleway.com/integrations/connectors/vercel-eve
Give a Vercel eve agent real-time web search, page extraction, and cited deep research with Nimble.
## Overview
[eve](https://eve.dev) is Vercel's filesystem-first framework for durable AI agents. An agent is a directory: instructions, tools, skills, and connections are all files. The [`@nimble-way/eve`](https://www.npmjs.com/package/@nimble-way/eve) extension adds Nimble web data to that directory in one line.
* **Mount once, get four tools**: search, extract, and two deep-research run tools.
* **Can take over eve's built-ins**: replace `web_search` and `web_fetch` so every turn uses Nimble. See [Replace the built-in web search and fetch](#replace-the-built-in-web-search-and-fetch).
* **Bundled skill**: teaches the agent when to search and when to extract.
* **Durable deep research**: start a Nimble Agent run, collect the cited answer minutes later.
Building a Vercel AI SDK app rather than an eve agent? Use the [Vercel AI SDK connector](/integrations/connectors/vercel-ai-sdk) instead. The two packages are separate: `@nimble-way/eve` targets eve, `@nimble-way/ai-sdk` targets the AI SDK directly.
## Prerequisites
eve needs **Node.js 24 or later**, newer than most Nimble integrations require. Run `node --version` first.
Create one with `npx eve@latest init my-agent`, or use an existing project. 0.27.8 is the extension's floor, but prefer a current release: eve ships frequently, and these examples are verified against 0.33.2.
Required by every tool. Get a key from the [dashboard](https://online.nimbleway.com/settings/api-keys). eve loads `.env` and `.env.local` automatically. The key resolves at call time, so `eve info` and builds work without it.
The key is the only credential. Unlike the [AI SDK package](/integrations/connectors/vercel-ai-sdk), the run tools need no pre-created [Nimble Agent](/nimble-sdk/web-search-agents/overview) and no `NIMBLE_AGENT_ID`. Omit `agentName` and Nimble creates an agent for the run, then returns its ID. Pass `agentName` to create or reuse a stable named agent instead.
## Quick Start
```bash theme={"system"}
npx eve@latest init my-agent
cd my-agent
```
```bash theme={"system"}
npm install @nimble-way/eve
```
```bash theme={"system"}
echo 'NIMBLE_API_KEY=your-api-key' >> .env.local
```
The filename sets the tool prefix. `nimble.ts` gives you `nimble__search`, `nimble__extract`, `nimble__agent_start`, and `nimble__agent_result`.
```ts theme={"system"}
// agent/extensions/nimble.ts
import nimble from '@nimble-way/eve';
export default nimble({});
```
```bash theme={"system"}
npx eve info
```
The output lists `nimble__search`, `nimble__extract`, `nimble__agent_start`, `nimble__agent_result`, and the `nimble__web-research` skill.
## How it works
eve derives identity from paths, so renaming the mount file renames every tool it contributes. Nothing declares a name.
The bundled skill tells the model to search when it does not know which page holds the answer, and to extract when it already has a URL.
Tools run in your app's Node process, so they read `NIMBLE_API_KEY` from your environment. Calls abort with the run.
Results return as structured output the model cites in its reply.
## Tools
Ranked web results with title, URL, and snippet. Full page content in deep mode.
Fetch a URL and return clean markdown or HTML, plus the links found on the page.
Start a Nimble Agent run. Returns identifiers immediately, without waiting.
Resume a started run and return the cited answer. Never creates a run.
The extension also contributes a `nimble__web-research` skill. eve loads it on demand through `load_skill`, and it recognizes both the namespaced and the promoted tool names.
### Search response
The extension normalizes the [Search API](/nimble-sdk/web-tools/search) response into camelCase and flattens the per-result metadata, so these shapes differ from the raw API reference.
```ts theme={"system"}
{
query: string;
requestId?: string;
totalResults?: number;
results: Array<{
title: string;
url: string;
description?: string;
content?: string; // deep searches only
position?: number;
entityType?: string;
}>;
}
```
### Extract response
```ts theme={"system"}
{
url: string; // final URL after redirects
status: string; // e.g. 'success'
statusCode?: number;
format: 'markdown' | 'html';
content: string; // truncated to maxContentLength
links?: string[];
}
```
### Deep research
These two tools drive a [Nimble Agent](/nimble-sdk/web-search-agents/overview) run: an autonomous agent that plans, searches, reads, and cross-checks many sources, then returns a cited answer.
**Reach for this instead of looping search and extract yourself.** When the question needs many sources, a run does the planning, deduplication, and cross-checking server-side, and returns [trust metadata](/nimble-sdk/web-search-agents/trust): per-claim citations and confidence, so the answer carries its own provenance. One billed run replaces a dozen model turns of search-then-read, so it is usually cheaper as well as better sourced. Use `nimble__search` for a quick fact and a run for a report.
`nimble__agent_start` takes an `input` task and optional `agentName`, `effort`, `skill`, `inputData`, `outputSchema`, `previousInteractionId`, and `sources`. It returns the run, agent, and interaction identifiers:
```ts theme={"system"}
{
runId: string;
agentId: string;
interactionId?: string;
}
```
Pass those to `nimble__agent_result`, which waits for a terminal state and returns the cited answer. The model calls both tools itself, so there is nothing to orchestrate. Splitting the lifecycle matters because a run takes minutes: eve records the identifiers from the first step before the second begins polling.
## Replace the built-in web search and fetch
eve ships `web_search` and `web_fetch` in its default harness. They differ from Nimble in reach and in what they return.
| | eve built-in | Nimble |
| - | - | - |
| `web_search` availability | Provider-managed, supported providers only | Every model |
| `web_search` backend | A provider-managed backend, currently Exa on AI Gateway models (eve 0.32.0) | Nimble Web Search |
| `web_fetch` rendering | Plain fetch, no JavaScript | Rendered page, cleaned content |
| `web_fetch` reach | Limited by eve's SSRF guard, which stops the agent reaching internal network addresses: HTTPS only, no private hosts, no automatic redirect following | Follows redirects and reports the final URL |
| Deep research | Not available | Nimble Agent runs with citations |
**Promote `web_search` in most agents.** The built-in only exists on supported model providers, so an agent that promotes it searches consistently on every model.
**Promote `web_fetch` when the agent reads real pages**, which is most research agents. Skip it when untrusted input can steer the agent to arbitrary URLs, because the swap replaces eve's SSRF guard. See [Promoting the fetch tool changes your security model](#promoting-the-fetch-tool-changes-your-security-model).
Optional. The Quick Start setup already works. Promote when you want every agent turn to use Nimble, with no instruction changes.
eve treats a file at `agent/tools/web_search.ts` as a replacement for its built-in of the same name. Only your own agent directory can replace a built-in, which is why the promote files live in `agent/tools/` and not inside the npm package.
Promoting does not remove the namespaced tool. The model would see both `web_search` and `nimble__search`, two entries doing the same job, and may call either. So each promotion needs two files: one to promote, one to disable the duplicate.
Disable files must live inside the extension mount, so the mount becomes a directory first:
1. Create `agent/extensions/nimble/extension.ts` with the same contents as the `nimble.ts` from Quick Start.
2. **Delete the old `agent/extensions/nimble.ts`.** Leaving it in place mounts the extension twice.
3. Add one disable file per promoted tool, named after the **extension's** tool (`search`, `extract`), not the built-in it replaced (`web_search`, `web_fetch`).
Promoting both tools gives five files:
```text theme={"system"}
agent/
extensions/
nimble/
extension.ts # mounts the extension
tools/
search.ts # disables nimble__search
extract.ts # disables nimble__extract
tools/
web_search.ts # Nimble Search in the built-in slot
web_fetch.ts # Nimble Extract in the built-in slot
```
```ts agent/extensions/nimble/extension.ts theme={"system"}
import nimble from '@nimble-way/eve';
export default nimble({});
```
```ts agent/tools/web_search.ts theme={"system"}
export { search as default } from '@nimble-way/eve/tools';
```
```ts agent/extensions/nimble/tools/search.ts theme={"system"}
import { disableTool } from 'eve/tools';
export default disableTool();
```
```ts agent/tools/web_fetch.ts theme={"system"}
export { extract as default } from '@nimble-way/eve/tools';
```
```ts agent/extensions/nimble/tools/extract.ts theme={"system"}
import { disableTool } from 'eve/tools';
export default disableTool();
```
`eve info` now lists `web_search` and `web_fetch`, both Nimble-backed.
### Promoting the fetch tool changes your security model
eve's built-in `web_fetch` has enforced an SSRF guard since eve 0.27.9. It requires HTTPS, rejects non-public destinations during DNS resolution, and returns redirect targets without following them. On 0.27.8, the extension's floor, that guard does not exist yet, so the tradeoff below does not apply. Upgrade rather than rely on it.
Promoting Nimble Extract into that slot replaces those checks. Fetches leave your app runtime and go through Nimble's infrastructure instead, which is what makes rendered pages and redirect following possible.
That is the right trade for most research agents, because the whole point is reading real pages. It is the wrong trade if the agent can be steered to arbitrary URLs by untrusted input. In that case, keep the built-in `web_fetch`, use `nimble__extract` alongside it, and let your instructions decide which to call. You can also gate the tool with `approval` from `eve/tools/approval`, or apply your own URL allowlist before the call.
Promoting `web_search` carries no equivalent tradeoff. It has no local executor to bypass.
### Promote one tool or both
**Default: promote `web_search`, keep the built-in `web_fetch`.** Replacing `web_search` costs nothing, because it has no local executor to bypass. Replacing `web_fetch` gives up eve's SSRF guard, which is worth it only when the agent's inputs are trusted.
To promote one tool, create only that tool's two files. The other stays available as `nimble__search` or `nimble__extract`.
## Configuration
Pass options where the extension is mounted. All are optional.
```ts theme={"system"}
// agent/extensions/nimble.ts
import nimble from '@nimble-way/eve';
export default nimble({
search: { depth: 'lite', maxResults: 5, country: 'US', locale: 'en' },
extract: { format: 'markdown' },
agent: { pollIntervalMs: 10_000, timeoutMs: 420_000 },
});
```
Nimble API credentials. Defaults to `process.env.NIMBLE_API_KEY`. Resolved at call time.
`'lite'` returns snippets (fast); `'deep'` returns full page content in results. Default `'lite'`.
Default result count when the model does not ask for a specific number. Default `5`.
Hard upper limit on what the model can request. Default `10`.
Per-result content truncation, in characters. Default `10_000`.
Result localization. Defaults `'US'` and `'en'`.
`'markdown'` for cleaned main content, or `'html'`. Default `'markdown'`.
Two-letter country code for geolocation and proxy selection. Optional.
Extracted content truncation, in characters. Default `50_000`.
Status polling interval for Nimble Agent runs. Default `10_000`. Runs take minutes, so a shorter interval mostly adds requests without returning the answer sooner. Polling counts against your [rate limit](/nimble-sdk/admin/rate-limits) like any other call.
Bounded deadline for reaching a terminal run state. Default `420_000`.
## Use the MCP server instead
Prefer no npm dependency? eve has first-class MCP connections, and the [Nimble MCP Server](/integrations/mcp-server/mcp-server) works with them as-is. That page owns the server reference: transport, auth, and the full tool inventory. This section covers only the eve wiring.
There are two ways to authenticate. Start with the API key, and switch to OAuth if you need it.
Create one file:
```ts theme={"system"}
// agent/connections/nimble.ts
import { defineMcpClientConnection } from 'eve/connections';
export default defineMcpClientConnection({
url: 'https://mcp.nimbleway.com/mcp',
description: 'Nimble web data: search the web, extract pages, map and crawl sites.',
auth: {
getToken: async () => ({ token: process.env.NIMBLE_API_KEY! }),
},
});
```
The Nimble MCP server also advertises [OAuth 2.1](/integrations/overview#oauth-sign-in), so [Vercel Connect](https://vercel.com/docs/connect) can own the consent flow and token storage. To use it, run:
```bash theme={"system"}
npm install @vercel/connect
vercel link
vercel connect create https://mcp.nimbleway.com/mcp --name nimble
vercel connect attach --yes
vercel env pull
```
Then swap the `auth` field in the same file for the Connect version:
```ts agent/connections/nimble.ts theme={"system"}
import { connect } from '@vercel/connect/eve';
// ...
auth: connect('mcp.nimbleway.com/nimble'),
```
eve gives the model a built-in `connection_search` tool that discovers these at run time, so there is nothing to register. It prefixes the connection name and leaves the remote name alone, so `nimble_search` on the server surfaces as `nimble__nimble_search`.
Narrow the surface with `tools.allow`, which takes bare remote names:
```ts agent/connections/nimble.ts theme={"system"}
export default defineMcpClientConnection({
url: 'https://mcp.nimbleway.com/mcp',
description: 'Nimble web data: search the web, extract pages, map and crawl sites.',
auth: { getToken: async () => ({ token: process.env.NIMBLE_API_KEY! }) },
tools: { allow: ['nimble_search', 'nimble_extract'] },
});
```
### Which path to pick
| | Extension | MCP connection |
| - | - | - |
| Install | `npm install @nimble-way/eve` | No dependency |
| Tool names | `nimble__search` | `nimble__nimble_search` |
| Promote to `web_search` / `web_fetch` | Yes | No |
| Tuned defaults and truncation caps | Yes | Server defaults |
| OAuth | No, API key | Yes, through Vercel Connect |
| Products | Search, Extract, Agent | [Everything the MCP server exposes](/integrations/mcp-server/mcp-server#tools) |
Use the extension for promoted built-ins and configurable defaults. Use MCP for a zero-install setup, OAuth, or access to Map and Crawl.
## What the extension covers
The extension ships Search, Extract, and Nimble Agent runs. Search returns ranked results at `lite` or `deep` depth, and Extract returns readable content and links.
For anything else, reach the full platform through the [MCP connection](#use-the-mcp-server-instead) or call the APIs directly: [Map](/nimble-sdk/web-tools/map) and [Crawl](/nimble-sdk/web-tools/crawl) for whole-site work, and the [focus modes](/nimble-sdk/web-tools/search#focus-modes) for news, social, and the other targeted searches.
## Before you ship
* **Page content stays data, not instructions.** The bundled `nimble__web-research` skill already tells the model to treat fetched pages as material to quote and reason over, never as commands. Repeat it in your own instructions for defense in depth.
* **Promoting `web_fetch` bypasses eve's SSRF guard.** See [Promoting the fetch tool changes your security model](#promoting-the-fetch-tool-changes-your-security-model).
* **Nimble Agent runs are billable.** Each `nimble__agent_start` call starts a paid run. The tool never retries a create, so a resumed poll cannot double-charge you.
* **Check your `eve` version.** The extension declares `eve` as a wildcard peer, so npm may install any release. Projects created with `eve init` are already pinned. If you added `eve` by hand, pin it in `package.json`.
## Resources
`@nimble-way/eve` on npm.
Source, README, and an example agent.
Vercel's framework documentation.
The hosted server behind the MCP path.
Nimble's underlying search capability.
Nimble's underlying page extraction capability.
The deep-research capability behind the run tools.
# Nimble Docs MCP Server
Source: https://docs.nimbleway.com/integrations/mcp-server/mcp-docs
Connect your AI tools to the Nimble documentation knowledge base via MCP
The Nimble Docs MCP server gives AI agents instant access to the full Nimble documentation including API references, guides, code examples, and feature explanations, all searchable in real time.
Connect it to Claude, Cursor, VS Code, or any MCP-compatible tool to ask questions about Nimble and get accurate, sourced answers without leaving your workflow.
**Server URL:** `https://docs.nimbleway.com/mcp`
## Available tools
Search across the Nimble Docs knowledge base to find relevant information, code examples, API references, and guides.
Use this tool when you need to answer questions about Nimble Docs, find specific documentation, understand how features work, or locate implementation details. The search returns contextual content with titles and direct links to the documentation pages.
**No API key required.** The Docs MCP is publicly accessible.
## Setup
No authentication required. Add the server URL to your preferred AI tool.
1. Open **Claude Settings** and navigate to the **Connectors** page.
2. Select **Add custom connector**.
3. Enter a name (e.g. `nimble-docs`) and the server URL:
```
https://docs.nimbleway.com/mcp
```
4. Select **Add**.
5. When chatting in Claude, select the attachments button (the **+** icon) and select your MCP server to activate it.
Run this command in your terminal:
```bash theme={"system"}
claude mcp add --transport http nimble-docs https://docs.nimbleway.com/mcp
```
The server is available in all Claude Code sessions immediately.
1. Open the command palette with `Command` + `Shift` + `P` (or `Ctrl` + `Shift` + `P` on Windows).
2. Search for **Open MCP settings**.
3. Select **Add custom MCP**. This opens `mcp.json`.
4. Add the following configuration:
```json theme={"system"}
{
"mcpServers": {
"nimble-docs": {
"url": "https://docs.nimbleway.com/mcp"
}
}
}
```
Restart Cursor for changes to take effect.
1. Create or open `.vscode/mcp.json` in your project.
2. Add the following configuration:
```json theme={"system"}
{
"servers": {
"nimble-docs": {
"type": "http",
"url": "https://docs.nimbleway.com/mcp"
}
}
}
```
## Example prompts
Once connected, ask your AI tool questions like:
* *"How do I extract a webpage with JavaScript rendering?"*
* *"What's the difference between async and batch extraction?"*
* *"Show me how to configure cloud storage delivery for extracted results."*
* *"What parameters does the Search API support?"*
* *"Which Extract Template returns Amazon product pages?"*
## Next steps
Connect AI agents to Nimble's web data platform for live extraction and
search
Token-efficient CLI skill for web retrieval in Claude Code and Cursor
# Nimble MCP Server
Source: https://docs.nimbleway.com/integrations/mcp-server/mcp-server
Connect AI agents to Nimble's web data platform via the Model Context Protocol
The Nimble MCP Server gives AI agents the ability to search, extract, map, crawl, and structure data from any website in real time. It exposes Nimble's full web data platform as MCP tools that any compatible AI client can use.
[MCP (Model Context Protocol)](https://modelcontextprotocol.io/) is an open standard by [Anthropic](https://www.anthropic.com/) that gives AI agents a standardized way to connect to external tools and data sources.
MCP routes results through the LLM context window and can consume tokens
quickly. For typical web retrieval, the [Nimble CLI
skill](/integrations/agent-skills/web-tools-skills/nimble-web-expert) is more token-efficient.
## Prerequisites
**Nimble API Key** — [Sign up](https://online.nimbleway.com/signup) and generate a key from your [Settings → API Keys](/nimble-sdk/admin/account-management#api-keys).
## Setup
The Nimble MCP Server uses **Streamable HTTP** transport. Connect from any MCP-compatible client using the server URL and your API key.
**Server URL:** `https://mcp.nimbleway.com/mcp`
For the one-click install plus the Nimble skills, see [Plugin Installation](/integrations/agent-skills/plugin-installation). To configure only the server by hand:
Add to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
```json theme={"system"}
{
"mcpServers": {
"nimble-mcp-server": {
"url": "https://mcp.nimbleway.com/mcp",
"headers": {
"Authorization": "Bearer NIMBLE_API_KEY"
}
}
}
}
```
Restart Cursor for changes to take effect.
To install the skills alongside the server, use [Plugin Installation](/integrations/agent-skills/plugin-installation). If you only need the MCP tools:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
claude mcp add --transport http nimble-mcp-server https://mcp.nimbleway.com/mcp \
--header "Authorization: Bearer ${NIMBLE_API_KEY}"
```
Add this to your `claude_desktop_config.json`:
```json theme={"system"}
{
"mcpServers": {
"nimble-mcp-server": {
"command": "npx",
"args": [
"-y", "mcp-remote@latest", "https://mcp.nimbleway.com/mcp",
"--header", "Authorization: Bearer ${NIMBLE_API_KEY}"
],
"env": {
"NIMBLE_API_KEY": "your-api-key"
}
}
}
}
```
* Replace `your-api-key` with your actual Nimble API key. Store the raw key; the `Bearer` prefix is added in the header, not in the value.
* **After modifying the config file, you must restart Claude Desktop for changes to take effect.**
* **macOS**: Quit Claude Desktop completely (Cmd+Q) and relaunch.
* **Windows**: Close the window, then open Task Manager (Ctrl+Shift+Esc), end any "Claude" processes, and relaunch.
For detailed setup instructions and troubleshooting, see the [official MCP quickstart guide](https://modelcontextprotocol.io/quickstart/user).
Any MCP-compatible client can connect using Streamable HTTP transport.
**Prerequisites:** [Node.js and npm](https://nodejs.org/) installed (for the `mcp-remote` bridge)
```json theme={"system"}
{
"mcpServers": {
"nimble-mcp-server": {
"command": "npx",
"args": [
"-y", "mcp-remote@latest", "https://mcp.nimbleway.com/mcp",
"--header", "Authorization: Bearer ${NIMBLE_API_KEY}"
],
"env": {
"NIMBLE_API_KEY": "your-api-key"
}
}
}
}
```
Replace `your-api-key` with your actual Nimble API key. Store the raw key; the `Bearer` prefix is added in the header, not in the value.
## Tools
The MCP server exposes Nimble's core APIs as tools, including several per product for synchronous, asynchronous, and status-polling calls. Tools are auto-discovered by your AI client, so no additional configuration is needed. Every tool name is prefixed `nimble_`.
Web search across multiple engines with configurable depth and focus modes.
Extract content from any URL. Supports sync and async extraction with task
polling.
Discover all URLs on a website via link-following and sitemaps.
Crawl multiple pages with path filtering, subdomain control, and progress
tracking.
Browse, run, and generate pre-built Extract Templates for popular websites.
Create, run, and poll research agents, and read their cited results.
## Plugin Installation
For a richer experience with guided skills and workflows, install the Nimble plugin for Claude Code, Cursor, or Vercel Agent Skills CLI.
Install the unified Nimble plugin for Claude Code, Cursor, or Agent Skills CLI
Create an Extract Template for any website, no coding required
Need enterprise scale or managed delivery?
# Choose Your Integration
Source: https://docs.nimbleway.com/integrations/overview
Pick the right way to connect Nimble to your coding agent, your app, or your warehouse
Nimble offers four integration paths. All four reach the same APIs. The right one depends on where your agent runs and how you want to authenticate.
Using a coding agent? [Paste one prompt into it](/nimble-sdk/getting-started/overview) and skip the reading below.
## Pick a path
| Path | Best for | Auth | Start here |
| - | - | - | - |
| **MCP server** | Chat assistants and production agents | **OAuth sign-in**, or an API key | [MCP Server](/integrations/mcp-server/mcp-server) |
| **Plugin + Skills** | Coding agents with terminal access | **OAuth sign-in**, or `NIMBLE_API_KEY` | [Plugin Installation](/integrations/agent-skills/plugin-installation) |
| **SDK + CLI** | Application code that needs full control | `NIMBLE_API_KEY` | [Quickstart](/nimble-sdk/getting-started/quickstart) |
| **Warehouse apps** | Data teams working in SQL | API key held in the warehouse | [Databricks](/integrations/partnerships/databricks/marketplace-mcp), [Snowflake](/integrations/partnerships/snowflake/native-app) |
Prefer OAuth where the client supports it. You sign in through the browser once and never handle a key, so there is nothing to paste, rotate, or leak. Tokens refresh automatically and you can revoke access at any time.
## Which path fits?
**Install the plugin.**
If your agent runs in a terminal, the plugin is the most complete setup. It installs two things at once: Agent Skills, which teach your agent which Nimble product fits a given job, and a pre-configured MCP server connection.
Skills matter more than they sound. Without them an agent knows the API exists but not when to reach for Extract Template instead of Extract, or an Agent instead of Search. The skills encode those choices.
[Plugin Installation](/integrations/agent-skills/plugin-installation) has a tab for each supported platform: Claude Code, Claude Cowork, Cursor, Codex, Cortex Code, and Vercel CLI.
For any other terminal agent, [install the CLI](/nimble-sdk/sdks/cli) and set `NIMBLE_API_KEY`. The CLI reaches every product, including several the MCP server does not carry. See the coverage table below.
**Use the MCP server, and sign in with OAuth.**
Chat assistants like Claude have no terminal, so they cannot run the CLI. They connect to outside tools over MCP instead.
Where the client supports OAuth, there is no key to handle at all. Click connect, approve the scopes in your browser, and the client stores and refreshes the token for you.
| Client | Setup |
| - | - |
| Claude Chat & CoWork | [Claude Connectors](/integrations/connectors/anthropic/claude-connectors) |
| VS Code and GitHub Copilot | [Azure MCP Center](/integrations/partnerships/microsoft/azure-mcp-center) |
| Microsoft Copilot Studio | [Copilot Studio](/integrations/partnerships/microsoft/copilot-studio) |
| Snowflake CoWork | [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) |
For clients without OAuth support, point them at the server and pass a key instead:
```json theme={"system"}
{
"mcpServers": {
"nimble": {
"type": "http",
"url": "https://mcp.nimbleway.com/mcp",
"headers": { "Authorization": "Bearer NIMBLE_API_KEY" }
}
}
}
```
See [MCP Server](/integrations/mcp-server/mcp-server) for Claude Desktop, Cursor, and other clients.
**Start with MCP for speed. Move to the SDK when you need control.**
Both paths are production-ready, and they are not exclusive. Many teams start with MCP, then move specific tools to the SDK as requirements tighten.
**MCP, fastest to ship.** OpenAI and Anthropic both accept a hosted MCP server URL directly in their APIs. Your model gets tool calling with no schemas to write or maintain.
**SDK, full control.** Use the SDK when you need custom tool schemas, your own response shaping, or a product the MCP server does not carry.
| Framework | Guide |
| - | - |
| OpenAI | [OpenAI connector](/integrations/connectors/openai) |
| Anthropic | [Anthropic SDK](/integrations/connectors/anthropic/sdk) |
| LangChain | [LangChain](/integrations/connectors/langchain) |
| LlamaIndex | [LlamaIndex](/integrations/connectors/llamaindex) |
| Vercel AI SDK | [Vercel AI SDK](/integrations/connectors/vercel-ai-sdk) |
| Vercel eve | [Vercel eve](/integrations/connectors/vercel-eve) |
| Google ADK | [Google ADK](/integrations/connectors/google-adk) |
| Mastra | [Mastra](/integrations/connectors/mastra) |
| Any framework | [Python](/nimble-sdk/sdks/python), [Node](/nimble-sdk/sdks/node), or [Go](/nimble-sdk/sdks/go) |
**Install the warehouse app and call the web from SQL.**
Nimble runs inside Databricks and Snowflake. Your API key stays in the warehouse, and results land as rows. No pipeline to build and no data leaves your account on the way in.
| Warehouse | Options |
| - | - |
| Databricks | [Marketplace MCP](/integrations/partnerships/databricks/marketplace-mcp), [Genie](/integrations/partnerships/databricks/genie), [Data enrichment](/integrations/partnerships/databricks/data-enrichment) |
| Snowflake | [Native App](/integrations/partnerships/snowflake/native-app), [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents), [Data enrichment](/integrations/partnerships/snowflake/data-enrichment) |
Snowflake reads your key from a Snowflake Secret you control. Databricks takes it as a bearer token.
## What each path can reach
Coverage is not equal. Pick a path that carries the products you need.
| Product | Skills | MCP | SDK | CLI |
| - | - | - | - | - |
| [Search](/nimble-sdk/web-tools/search) | Yes | Yes | Yes | Yes |
| [Extract](/nimble-sdk/web-tools/extract/quickstart) | Yes | Yes | Yes | Yes |
| [Extract Template](/nimble-sdk/web-tools/extract/template) | Yes | Yes | Yes | Yes |
| [Map](/nimble-sdk/web-tools/map) | Yes | Yes | Yes | Yes |
| [Crawl](/nimble-sdk/web-tools/crawl) | Yes | Yes | Yes | Yes |
| [Agent](/nimble-sdk/web-search-agents/overview) | Yes | Yes | Yes | Yes |
| [SERP](/nimble-sdk/web-tools/serp) | No | No | Yes | Yes |
| [Media](/nimble-sdk/web-tools/media) | No | No | Yes | Yes |
| [Domain Knowledge](/nimble-sdk/web-tools/domain-knowledge) | No | No | Yes | Yes |
| [Jobs](/nimble-sdk/agentic/jobs) | No | No | Yes | Yes |
## Authentication
Two mechanisms cover every path. Use OAuth where you can and an API key where you must.
### OAuth sign-in
The MCP server supports OAuth 2.1 with PKCE. Nothing to configure: the client discovers the flow from the server, opens your browser, and stores the token itself.
| | |
| - | - |
| **Scopes** | `nimble:read`, `nimble:write`, `offline_access`, `openid` |
| **Refresh** | Handled by the client. `offline_access` keeps access alive between sessions. |
| **Revoke** | Disconnect from the client, or revoke from your Nimble account. |
| **Works with** | Claude, VS Code and GitHub Copilot, Copilot Studio, Cortex Code |
Approving the consent screen grants permission to verify your identity, search the web, extract data, and manage Agents.
### API key
| Path | How it works |
| - | - |
| **SDK + CLI** | Set `NIMBLE_API_KEY`, or pass the key to the client constructor. |
| **MCP server** | Pass the key as `Authorization: Bearer ` for clients without OAuth. |
| **Plugin + Skills** | Set `NIMBLE_API_KEY`. The CLI and skills read it automatically. |
| **Warehouse** | Snowflake reads a Snowflake Secret. Databricks takes a bearer token. |
| **Proxy** | Credentials go in the connection string, not a header. See [Proxy authentication](/nimble-sdk/web-tools/proxy/authentication). |
Get a key from [Settings → API Keys](https://online.nimbleway.com/settings/api-keys). Issue a separate key per project so you can revoke one without breaking the rest.
## Using more than one path
Most teams do. A plugin for local development, MCP for a chat assistant, and the SDK for production code all hit the same APIs with the same key.
## Next steps
Install skills and MCP config in your coding agent
Give any AI agent live web access
Your first API call in under a minute
Wire Nimble into your agent framework
# Databricks
Source: https://docs.nimbleway.com/integrations/partnerships/databricks
Bring Nimble's live web tools into the Databricks Lakehouse. Search, extract, map, crawl, and Nimble Agents run next to your Delta tables, agents, and Genie spaces.
Nimble brings the live web into the Databricks Lakehouse. Search, page extraction, site maps, crawls, and Nimble Agents sit alongside your Delta tables, Unity Catalog governance, and AI agents — so the AI you build inside Databricks can ground its answers in current external signal, not just historical data.
## Why web data belongs next to the Lakehouse
Databricks is great at orchestrating the data you already own. But the world your data describes lives outside the Lakehouse, and most of the questions stakeholders actually ask need both. Nimble closes that gap without leaving the Databricks ecosystem:
* **Enrich Delta tables in place.** Pull current company metadata, product details, or pricing for the rows in a Unity Catalog table, and land the results straight back as a governed Delta table.
* **Ground agents in live signal.** Give a Databricks-hosted agent the same Nimble tools you'd use yourself, so it can research, verify, and gather external context as part of its workflow.
* **Govern every web call.** Route Nimble through Unity AI Gateway so every request inherits Databricks-native audit, lineage, and access control.
* **Stay in SQL.** Call Nimble as table functions inside any `SELECT`, so notebooks, dashboards, dbt models, Workflows, and Genie consume the output transparently.
## Pick a surface
Install Nimble MCP Server from the Databricks Marketplace as a one-click Unity Catalog connection. Govern it with Unity AI Gateway, then test it in AI Playground and Databricks Assistant. The install foundation every other surface builds on.
Ask the live web in plain English from Genie One. Enable the Nimble MCP connection and business users get cited, web-grounded answers — no SQL, no notebook. The natural-language surface for the whole integration.
Call Nimble as SQL-native table functions in Unity Catalog. `nimble_search`, `nimble_extract`, and `nimble_agent_run` run live web search inside a `SELECT` and land results in governed Delta tables — dbt-native, Workflow-native, Genie-native.
Turn a plain-English brief into a full data product inside Genie Code, Databricks' agentic coding agent. Install the Nimble custom skill and it discovers the right Extract Templates, ingests live web data into Delta, and builds a dashboard or app — end to end. The "never leave Databricks" build surface.
## When to use which surface
The Databricks integration spans four surfaces over one Nimble connection. Pick by question:
| You want to… | Use | Page |
| - | - | - |
| Give an agent, AI Playground, or Databricks Assistant live web tools through a governed MCP connection | **Marketplace & MCP** install | [Marketplace & MCP](/integrations/partnerships/databricks/marketplace-mcp) |
| Let a business user ask live-web questions in plain English and get cited answers | **Genie One** + the Nimble MCP connection | [Genie One](/integrations/partnerships/databricks/genie) |
| Enrich N warehouse rows with structured web fields, or land web data in Delta tables — in pure SQL | **`nimble_search`** / **`nimble_extract`** / **`nimble_agent_run`** table functions | [Data Enrichment](/integrations/partnerships/databricks/data-enrichment) |
| Turn a plain-English brief into a full data product — table, dashboard, and app — inside the agentic coding agent | **Genie Code** + the Nimble custom skill | [Genie Code](/integrations/partnerships/databricks/genie-code) |
All four share the same Nimble platform underneath — the difference is who drives the calls: an LLM agent (MCP), a business user in natural language (Genie One), your SQL pipeline (table functions), or an agentic coding agent that builds the whole product (Genie Code).
## Resources
Install Nimble MCP Server directly from the Marketplace
Full MCP Server setup for Cursor, Claude, and other clients
Databricks documentation for external MCP server connections
Overview of templates — what they are, when to use them, how they parse
# Data Enrichment
Source: https://docs.nimbleway.com/integrations/partnerships/databricks/data-enrichment
Call Nimble as SQL table functions in Databricks — live web search, page extraction, and Extract Templates that land in governed Delta tables.
This integration is ideal for data engineers who need live web search inside their Databricks pipelines — without leaving SQL or building a custom API integration.
Nimble ships as a set of SQL-native **table functions** in Unity Catalog. `SELECT … FROM nimble_search(…)` and `SELECT … FROM nimble_extract(…)` run live web search and page extraction directly inside a query, and results compose with `CREATE TABLE … AS` to land in a **governed Delta table** — access-controlled, lineage-tracked, time-travelable. The same shape exposes every [template](/nimble-sdk/web-tools/extract/template) for structured, per-row enrichment.
```sql Live web search and page extraction, straight from SQL theme={"system"}
-- Web search → rows of (title, description, url, content)
SELECT title, url
FROM nimble_integration.tools.nimble_search('AI agent frameworks news', 5, 'news');
-- Enrich a table of URLs and persist as a governed Delta table
CREATE TABLE my_catalog.web.page_text AS
SELECT u.id, x.url, x.content
FROM my_catalog.web.urls u,
LATERAL nimble_integration.tools.nimble_extract(u.url) x;
```
Every capability is a table function — call it in the `FROM` clause. No stored procedures, no Python middle-tier, no `http_request()` + `from_json` plumbing. Notebooks, dashboards, dbt models, Databricks Workflows, and Databricks Genie all consume the output transparently.
## What you get
* **Search and extract as first-class SQL.** `nimble_search(query, …)` returns rows of `title, description, url, content`; `nimble_extract(url, …)` returns a row of `url, format, content` (markdown / html / links). Both hit the live web through Nimble at query time.
* **Results land as governed Delta tables.** Wrap any call in `CREATE TABLE … AS` and the output is a managed Unity Catalog table — Delta time travel, column/table lineage, and `GRANT`-based access control come for free.
* **Every [Extract Template](/nimble-sdk/agentic/agent-gallery), one function.** `nimble_agent_run(agent, params_json)` runs any Extract Template (`google_maps_search`, `amazon_serp`, `linkedin_company_details`, …) and returns the parsed payload as a navigable `VARIANT`. `nimble_agent_list()` enumerates the catalog and `nimble_agent_describe(agent)` returns a template's exact input parameters. Despite the `nimble_agent_*` names, these functions operate on Extract Templates, not Nimble Agents.
* **Per-row error isolation.** Each tool yields **zero rows on failure** instead of raising, so a batch `CREATE TABLE … AS` over thousands of inputs is never aborted by one bad row.
* **Genie-native.** Databricks Genie registers table functions as tools, so each function is directly registrable as a Genie agent tool — the function and column `COMMENT`s are the spec the LLM reads.
* **Stays inside your Databricks account.** Only authorized outbound calls hit Nimble. The API key lives in a Databricks secret scope and is injected server-side — it never appears in a function signature or at a call site.
## Prerequisites
* A Databricks workspace with a **serverless SQL warehouse**.
* Privilege to create a catalog/schema (or an existing schema you own) plus `CREATE FUNCTION` on it.
* The **Databricks CLI v0.205+**, authenticated (`databricks auth login`), and `jq` (used by the deploy snippet to resolve a warehouse id).
* A Nimble API key ([sign up free](https://online.nimbleway.com/signup)).
**Enable outbound networking for the warehouse — or every tool returns zero rows.** A serverless SQL warehouse blocks Python UDF/UDTF egress by default. Two one-time steps fix it:
1. **Workspace Settings → Previews** → enable **"Enable networking for isolated workloads in Serverless SQL Warehouses"** (Public Preview).
2. **Cold-restart the warehouse** — fully **Stop** it, then **Start** it. A plain "restart" is *not* enough; the new network config is only picked up on a cold start.
Symptom of skipping this: a tool returns zero rows and the underlying request fails with `Connection refused` (Errno 111) even though DNS resolves. The account-level serverless egress *network policy* is a separate control and is usually already "allow all" — it is not this setting.
Workspaces that can't enable the preview can use the [`http_request()` fallback](#fallback-http-request-without-the-networking-preview) instead.
## Install in Databricks
The tools read the key from `secret('nimble','api_key')`, so no token ever appears in a function body or at a call site. Create the scope once per workspace.
```bash theme={"system"}
databricks secrets create-scope nimble
databricks secrets put-secret nimble api_key # paste the raw token, then Ctrl-D
databricks secrets put-acl nimble users READ
```
`READ` lets every workspace user *call* `secret('nimble','api_key')` from SQL — values come back **redacted** in any display or log, so the plaintext can't be extracted. Replace `users` with a group name to scope access more tightly.
All the SQL and the deploy helper live in the Nimble cookbook. Clone it and work from the repo root — the deploy commands below use paths relative to it.
```bash theme={"system"}
git clone https://github.com/Nimbleway/cookbook.git
cd cookbook
```
One loop deploys everything. `01_setup.sql` creates the `nimble_integration` catalog and the `tools` + `recipes` schemas; each file in `tools/` then installs one table function. `deploy_sql.py` posts each multi-statement file to your warehouse (it treats the `$$ … $$` UDTF bodies as opaque), and all DDL is re-runnable (`CREATE … IF NOT EXISTS`).
```bash theme={"system"}
# Grab a warehouse id automatically (first one), or set WH explicitly. Needs jq.
WH=$(databricks warehouses list -o json | jq -r '.[0].id')
for f in databricks/01_setup.sql databricks/tools/*.sql; do
python3 databricks/helpers/deploy_sql.py --file "$f" --warehouse "$WH"
done
```
Each tool is a Python **UDTF** (`_nimble_xxx`) that makes the HTTP call, behind a thin SQL `RETURNS TABLE` wrapper (`nimble_xxx`) that supplies defaults and injects the API key. The wrapper is what you call and what Genie registers — there is no scalar twin. `nimble_search` defaults to `search_depth = lite` (`fast` / `deep` are premium and not enabled on every account).
If your workspace uses Unity Catalog **Default Storage**, the `01_setup.sql` deploy may fail with *"Metastore storage root URL does not exist."* Create the catalog once from Catalog Explorer (**Create Catalog → Default Storage**, name it `nimble_integration`) — or use the explicit `CREATE CATALOG nimble_integration MANAGED LOCATION ''` form pointed at one of your UC external locations — then re-run the loop.
`nimble_search.sql`, `nimble_extract.sql`, `nimble_agent_list.sql`, `nimble_agent_describe.sql`, `nimble_agent_run.sql` — full UDTF + wrapper for each, with COMMENTs and smoke tests
Both should return real results within \~30 seconds.
```sql theme={"system"}
-- Web search → expect ~10 rows of live results (title + url populated)
SELECT title, url
FROM nimble_integration.tools.nimble_search('AI agent frameworks news', 10);
-- Page extraction → expect a snippet of real extracted markdown
SELECT substr(content, 1, 300) AS preview
FROM nimble_integration.tools.nimble_extract('https://www.nimbleway.com');
```
If a tool returns zero rows, re-check the networking Preview + cold restart from [Prerequisites](#prerequisites).
## The tools
| Function | What it does |
| - | - |
| `nimble_search(query, max_results, focus, search_depth, …)` | Live web search → rows of `title, description, url, content`. |
| `nimble_extract(url, render, format, …)` | Fetch + parse one URL → a row of `url, format, content` (markdown / html / links). |
| `nimble_agent_list(privacy, managed_by, …)` | The Extract Template catalog → one row per template. |
| `nimble_agent_describe(agent_name)` | A single Extract Template's input parameters → one row per param (`param_name`, `required`, `type`, localization/pagination flags). Use it to build a valid `nimble_agent_run` `params_json` without guessing. |
| `nimble_agent_run(agent, params_json, localization)` | Run any Extract Template → one row: response envelope + `parsing` (VARIANT). |
**Two functions per tool.** Each capability is a Python UDTF (`_nimble_xxx`, does the HTTP call and yields rows) behind a thin SQL `RETURNS TABLE` wrapper (`nimble_xxx`). The wrapper does three things the UDTF can't: supply `DEFAULT` parameter values (UC Python UDTFs reject them), inject `secret('nimble','api_key')` once (a UDTF can't read `secret()` itself), and — for `nimble_agent_run` — `parse_json()` the payload into a navigable `VARIANT` (a Python UDF/UDTF can't *return* `VARIANT`). You only ever call the public wrapper.
## Common workflows
### Land web data as a governed Delta table
The payoff of SQL-native web data: results compose with `CREATE TABLE … AS` and inherit Unity Catalog governance — access control, lineage, and time travel. Enrich a table of company homepages into a managed Delta table in one statement:
```sql theme={"system"}
CREATE TABLE my_catalog.web.company_pages AS
SELECT c.company_id,
c.homepage,
x.content AS homepage_markdown,
current_timestamp() AS fetched_at
FROM my_catalog.crm.companies c,
LATERAL nimble_integration.tools.nimble_extract(c.homepage) x;
```
Because `nimble_extract` yields zero rows on a failed fetch (instead of raising), the batch over every company is never aborted by one bad URL. The result is a real Unity Catalog Delta table: `DESCRIBE HISTORY` it for time travel, `GRANT SELECT` it to a group, and trace its column lineage back through `nimble_extract` to `companies`.
### Local business discovery at scale
Build a fresh, location-aware view of every business that matters for a category in a market: feed a `location_queries` table of research terms and get back the ranked Google Maps landscape — one row per business per query. Useful for market sizing, account-universe building, competitive territory mapping, and POI databases.
`google_maps_search` returns \~20 results per query under `parsing:entities:SearchResult`. `nimble_agent_run` is a table function whose `parsing` column is a `VARIANT`, so navigate it with `:` paths and explode the results array with `LATERAL variant_explode` — one row per business — then persist with `CREATE TABLE … AS`.
```sql theme={"system"}
CREATE OR REPLACE TABLE nimble_integration.recipes.local_businesses AS
SELECT
q.query,
q.category,
biz:position::int AS position,
biz:title::string AS name,
biz:business_category[0]::string AS business_category,
biz:street_address::string AS street_address,
biz:city::string AS city,
biz:review_summary:overall_rating::double AS rating,
coalesce(biz:review_summary:review_count::int,
biz:number_of_reviews::int) AS review_count,
biz:price_level::string AS price_level,
biz:place_information:website_url::string AS website,
biz:business_status::string AS business_status,
biz:sponsored::boolean AS sponsored,
current_timestamp() AS enriched_at
FROM nimble_integration.recipes.location_queries q,
LATERAL nimble_integration.tools.nimble_agent_run(
'google_maps_search',
to_json(named_struct('query', q.query))
) a,
LATERAL variant_explode(a.parsing:entities:SearchResult) AS e(pos, key, biz)
WHERE a.status = 'success';
```
**Input: `location_queries`**
| query | category |
| - | - |
| coffee shops in Williamsburg Brooklyn | coffee |
| pizza restaurants in Chicago Loop | pizza |
| gyms in Austin Texas | gym |
**Output: `local_businesses`** (one row per business per refresh)
| query | position | name | city | rating | review\_count | price\_level |
| - | - | - | - | - | - | - |
| coffee shops in Williamsburg Brooklyn | 1 | Devoción | Brooklyn | 4.5 | 2481 | \$\$ |
| coffee shops in Williamsburg Brooklyn | 2 | Sey Coffee | Brooklyn | 4.6 | 1840 | \$\$ |
| pizza restaurants in Chicago Loop | 1 | Giordano's | Chicago | 4.4 | 10618 | \$\$ |
`google_maps_search` nests results under `parsing:entities:SearchResult` — not a flat array. `LATERAL variant_explode(a.parsing:entities:SearchResult)` is the correct unfold path. Different templates nest results differently; inspect once with `SELECT parsing FROM nimble_agent_run(, …)` before pinning the explode target and projection.
Full runnable recipe: the `location_queries` seed, the stitch-and-explode CTAS, sanity-check queries, and the governance proof points (time travel + lineage + GRANT)
### Structured per-row enrichment with any Extract Template
When there is no domain-specific need beyond a single identifier, `nimble_agent_run` enriches each input row with that template's typed payload. PDP-style templates (Amazon PDP, LinkedIn Profile, Walmart PDP) return a single object; navigate it inline with `:` paths and cast leaves with `::type`.
```sql theme={"system"}
SELECT
p.sku,
a.parsing:brand::string AS brand,
a.parsing:product_title::string AS title,
a.parsing:web_price::decimal(10, 2) AS price,
a.parsing:availability::boolean AS in_stock,
a.parsing:average_of_reviews::double AS rating
FROM my_catalog.crm.products p,
LATERAL nimble_integration.tools.nimble_agent_run(
'amazon_pdp',
to_json(named_struct('asin', p.amazon_asin))
) a
WHERE p.amazon_asin IS NOT NULL
AND a.status = 'success';
```
One input row → one output row, no `variant_explode` needed. Param and field names differ per template (`amazon_pdp` takes `asin`; `product_title` vs. `product_name`, `web_price` vs. `price`) — discover the catalog with `SELECT * FROM nimble_agent_list()`, read a template's inputs with `SELECT * FROM nimble_agent_describe('')`, and inspect a single response with `SELECT parsing FROM nimble_agent_run(, …)` before pinning a projection.
### Schedule recurring enrichment
Each tool runs in a single SQL statement, so a [Databricks Workflow](https://docs.databricks.com/aws/en/jobs/) can execute the enrichment directly on a schedule — point a SQL task at the `CREATE OR REPLACE TABLE … AS` statement above and set a cron trigger. No wrapper procedure required. The same statement also drops straight into a dbt model body for orchestrated, incremental refreshes.
## Fallback: `http_request()` without the networking preview
If a workspace can't enable UDTF egress, the `http_request()` SQL builtin reaches the Nimble API through a different, always-on egress path — at the cost of `from_json` parsing in SQL and no per-row isolation. It needs a one-time UC HTTP `CONNECTION` (requires the `CREATE CONNECTION` privilege):
```sql theme={"system"}
CREATE CONNECTION IF NOT EXISTS nimble_api TYPE HTTP
OPTIONS (
host 'https://sdk.nimbleway.com',
port '443',
base_path '/',
bearer_token secret('nimble', 'api_key')
);
```
A fallback query then calls `http_request(conn => 'nimble_api', method => 'POST', path => '/v2/search', headers => map('Content-Type','application/json'), json => to_json(named_struct(…)))`, gates on `response.status_code`, and parses `response.text` with `from_json`. See `ADDING_A_TOOL.md` in the cookbook for the full pattern.
## Roll out across your organization
* **Tune for your Nimble rate-limit tier.** Each tool call is one upstream request — one per input row. For high-cardinality inputs (thousands of rows), watch for 429s; because failures yield zero rows, a single retry pass over the rows that produced no output is the typical recovery. Nimble's [rate-limits page](/nimble-sdk/admin/rate-limits) lists per-tier ceilings.
* **Right-size the warehouse.** A small serverless warehouse handles daily refreshes over a few hundred input rows. For larger enrichments (10K+ rows), step up the size only for the duration of the scheduled job.
* **Surface failures separately.** Because tools yield zero rows on failure, an input row with no matching output row is a failed call — left-join the input table against the result to find the gaps and audit them, rather than grepping job logs.
* **Don't ship guesses.** Input params and `parsing` field names differ per template. Read the inputs with `nimble_agent_describe('')` and inspect the output shape once (`SELECT parsing FROM nimble_agent_run(, …)`) before pinning a projection into a dbt model or production view.
## Use it from Databricks Genie
Genie registers **table functions** as tools, so each public function is directly registrable — point a Genie space at `nimble_integration.tools.nimble_search` (and the others). The function `COMMENT` and per-column `COMMENT`s are what the LLM reads to decide when and how to call each tool. The cookbook ships `helpers/create_genie_space.py` to create a space wired to all five functions programmatically.
## Resources
Every SQL file on this page, the one-time prereqs, the local-business recipe, and the deploy helpers
The agent-side surface: Nimble MCP Server from the Databricks Marketplace, wired into Genie and AI Playground
Overview of templates — what they are, when to use them, how they parse
Browse the catalog of pre-built templates and their per-template parsing shapes
How Unity Catalog tabular UDFs work — `eval()` handler, `RETURNS TABLE`, and network egress
Register these table functions as tools in a Genie space for natural-language web data
# Genie One
Source: https://docs.nimbleway.com/integrations/partnerships/databricks/genie
Ask the live web in plain English from Databricks Genie One. Connect the Nimble MCP server and Genie answers with search, extraction, and Nimble Agents — with cited sources.
[Genie One](https://docs.databricks.com/aws/en/genie/) — the natural-language assistant in Databricks One — answers business questions over your data without anyone writing SQL. Connect the Nimble MCP server and Genie can also reach the **live web**: ask a question in plain English and Genie calls Nimble's search, extraction, and Agent tools, then answers with cited sources. No notebook, no SQL, no leaving Databricks.
## What you get
* **The live web in natural language.** Business users ask Genie a question and get an answer grounded in current web data — no SQL, no tool names, no API keys.
* **Reuses the governed Nimble connection.** Genie calls the same Unity Catalog MCP connection installed from the [Marketplace](/integrations/partnerships/databricks/marketplace-mcp) — so every web call inherits the same Unity AI Gateway audit, access control, and governance.
* **Cited, inspectable answers.** Genie shows each tool call (request and response) inline and cites the source URLs in its final answer.
* **Stays inside Databricks.** Only authorized outbound calls hit Nimble; the connection and its credentials live in Unity Catalog.
## Prerequisites
* The **Nimble MCP server installed as a Unity Catalog connection** — follow [Marketplace & MCP](/integrations/partnerships/databricks/marketplace-mcp) first. Genie connects to that connection (for example `nimble-mcp-marketplace`); it does not create its own.
* Access to **Genie One** in your Databricks One workspace.
* **USE CONNECTION** on the Nimble connection (granted on the [Marketplace & MCP](/integrations/partnerships/databricks/marketplace-mcp#share-the-connection) page).
**MCP connections in Genie are in Beta.** As surfaced in the Set Connections dialog: custom MCP connections do not yet support per-action read/write approvals, so only enable connections you trust to act on your behalf. Treat this surface as preview while the feature stabilizes.
## Connect Nimble in Genie One
From the Databricks One workspace switcher (top-right), choose **Genie One — Business insights from data and AI**. The Genie home opens with a single **Ask a question…** box.
In the question box, click the **+** button and choose **More connections…**. The **Set Connections** dialog lists the MCP server connections available to you.
In **Set Connections**, find and check the Nimble connection (for example `nimble-mcp-marketplace`). A Beta banner notes that custom MCP connections act on your behalf without per-action approval — enable only connections you trust.
Type a question that needs the open web — Genie picks the right Nimble tool, calls it, and answers. You don't name the tool; Genie reads the connection's tool descriptions and decides.
## See it work
Ask Genie a question that the warehouse alone can't answer — something that depends on what's live on the web right now:
> Which noise-cancelling headphones are top-rated this year, and how do their current prices compare? Use Nimble and cite sources.
Genie routes it to the Nimble MCP server and shows the call inline:
* **Calling tool → Running Nimble Search.** Genie expands a thinking trace as it works.
* **Request / Response.** Each completed call shows the exact request Genie sent (`{"query": "best noise-cancelling headphones price comparison", "max_results": 8, "search_depth": "deep", "focus": "shopping"}`) and the raw response — full transparency into what was fetched.
* **Cited answer.** Genie synthesizes the results into a ranked comparison with prices, ratings, and source citations.
Because Genie calls Nimble through the governed Unity Catalog connection, every one of these tool calls is auditable in the [Unity AI Gateway audit table](/integrations/partnerships/databricks/marketplace-mcp#governance-with-unity-ai-gateway) — same as any other proxied Nimble request.
## Try these questions
Paste any of these into Genie One once the Nimble connection is enabled. Each forces a different Nimble tool.
"Search the web for the latest news about \ and summarize what changed this month, with sources."
"Use Nimble to find \'s current headquarters, employee count, and latest funding round, and cite where each fact came from."
"Extract the pricing page at \ and list each plan with its price and key limits."
"Find the top-rated coffee shops in Austin, Texas with their ratings and review counts."
## Roll out across your organization
* **Install once, share the connection.** The Nimble MCP connection is a single Unity Catalog object. Grant **USE CONNECTION** to the business-user groups who should query the web in Genie; they never see or manage the API key.
* **Govern through Unity AI Gateway.** Every Genie-initiated Nimble call routes through the managed proxy and lands in `system.access.audit`. Use the **AI Gateway Usage Analytics** dashboard to track web-tool usage by user — see [Marketplace & MCP](/integrations/partnerships/databricks/marketplace-mcp#governance-with-unity-ai-gateway).
* **Mind the Beta limits.** Custom MCP connections in Genie don't yet gate individual read/write actions behind approval. Until that ships, scope `USE CONNECTION` deliberately and prefer it for read-oriented research use cases.
* **Prefer SQL for batch?** When the job is enriching many rows rather than answering one question, the same Nimble capabilities are available as Unity Catalog table functions — see [Data Enrichment](/integrations/partnerships/databricks/data-enrichment).
## Resources
Install the Nimble MCP connection that Genie connects to, and govern it with Unity AI Gateway
The same Nimble tools as Unity Catalog table functions, for SQL-native batch enrichment
Databricks documentation for Genie and Genie One
How Databricks connects Genie and agents to MCP servers
Full MCP Server setup for Claude, Cursor, and other clients
Setup SQL, tools, and recipes behind the Databricks integration
# Genie Code
Source: https://docs.nimbleway.com/integrations/partnerships/databricks/genie-code
Build Databricks data products from live web search in Genie Code. Install the Nimble custom Agent Skill and describe the goal in plain English — discover the right Extract Templates, ingest into Delta, and build a dashboard or app, end to end.
[Genie Code](https://docs.databricks.com/aws/en/genie-code/) — Databricks' agentic coding agent — writes notebooks, explores Unity Catalog, builds dashboards, and deploys apps from plain-English instructions. It extends with [custom Agent Skills](https://docs.databricks.com/aws/en/genie-code/skills), and Nimble ships one: install it, describe a goal, and Genie Code turns it into a working data product — discover the right Extract Templates, ingest live web data into a Delta table, and build an AI/BI dashboard or a deployed app. End to end, without leaving Databricks.
Where [Genie One](/integrations/partnerships/databricks/genie) *answers* live-web questions, Genie Code *builds* — it stands up the table, the dashboard, and the app for you.
## What you get
* **A brief becomes a data product.** "Pricing comparison on dog products from Amazon and Walmart" → a governed Delta table, an AI/BI dashboard, and optionally a deployed app.
* **Native execution, nothing extra to install.** The skill runs as inline SQL over the Nimble [Unity Catalog table functions](/integrations/partnerships/databricks/data-enrichment), and hands dashboards and apps to Genie Code's own dashboard agent and AppsAgent. No external tooling.
* **Discovery, not guesswork.** It reads the live [Extract Template](/nimble-sdk/web-tools/extract/template) catalog at runtime (`nimble_agent_list`, `nimble_agent_describe`) and probes one call per source before fanning out — so it uses each template's real parameters and fields. Despite the `nimble_agent_*` names, these functions operate on Extract Templates, not Nimble Agents.
* **Set-based ingestion.** A control table plus one correlated `LATERAL nimble_agent_run` INSERT loads every source in parallel and lands a unified, normalized Delta table — reproducible and expandable.
* **The "never leave Databricks" story, end to end.** From a sentence to a published dashboard, all inside Genie Code.
## Prerequisites
* The **Nimble × Databricks integration** installed — the `nimble_integration.tools.*` table functions. See [Data Enrichment](/integrations/partnerships/databricks/data-enrichment) for setup. The skill's first step verifies they exist and stops with guidance if not.
* **Genie Code in [Agent mode](https://docs.databricks.com/aws/en/genie-code/use-genie-code)** — custom skills load only in Agent mode.
## Install the skill
Genie Code loads skills from a `.assistant/skills/` directory. Copy the Nimble skill folder there — keep the folder name `nimble-search`.
* **User skill** (just you; the prototype path): `/Users/{username}/.assistant/skills/`
* **Workspace skill** (org-wide; admins): `Workspace/.assistant/skills/`
The quickest path: open the skills folder from the Genie Code panel (**⚙ Settings → Open skills folder**) and copy the folder in, then start a **new** Agent-mode chat. To keep it updated, back the skills folder with a [Databricks Git folder](https://docs.databricks.com/aws/en/repos/) pointed at the cookbook.
The `nimble-search` skill (SKILL.md + references) and full install instructions
## Ask in plain English
In a Genie Code Agent-mode chat, describe the goal — the skill auto-loads by its description (or `@`-mention it):
* `pricing comparison on dog products from Amazon and Walmart`
* `extract Zillow listings for Austin into a Delta table and build a dashboard`
* `show competitor prices from the web in a dashboard`
Genie Code confirms the deliverable (table / dashboard / app), the sources, and the target schema, then runs the flow and hands back the links and the headline insight.
## How it works
1. **Discover** — `nimble_agent_list()` finds the Extract Templates matching your sources; `nimble_agent_describe()` reads each one's exact input parameters.
2. **Probe** — one `nimble_agent_run` call per source confirms the real output fields and localization before fanning out.
3. **Ingest** — a control table drives a single correlated `LATERAL nimble_agent_run` INSERT that loads every source in parallel into one unified, normalized Delta table.
4. **Build** — Genie Code's native dashboard agent assembles an AI/BI dashboard from the table; its AppsAgent can deploy an app (Python frameworks by default — Streamlit / Dash / Gradio — with React supported build-less).
5. **Deliver** — the published dashboard and running app links.
## Resources
The Nimble Genie Code skill and install guide
Databricks' guide to creating and installing custom Genie Code skills
The `nimble_integration.tools.*` table functions this skill builds on
The same workflow as a published skill for other AI coding assistants
# Marketplace & MCP
Source: https://docs.nimbleway.com/integrations/partnerships/databricks/marketplace-mcp
Install Nimble MCP Server from the Databricks Marketplace, govern it with Unity AI Gateway, and test it in AI Playground and Databricks Assistant.
Nimble MCP Server is available on the [Databricks Marketplace](https://marketplace.databricks.com/details/1fe5e521-e9ef-49d8-97c4-b8c0a448ce15/Nimble_Nimble-MCP-Agentic-Web-Search-Platform) as a one-click install. It creates a secure Unity Catalog connection that gives any Databricks agent access to Nimble's full web data platform — search, extract, map, crawl, and structured data extraction. This is the install foundation every other Databricks surface builds on.
## Prerequisites
* A Databricks workspace with the **Managed MCP Servers** preview enabled ([manage previews](https://docs.databricks.com/aws/en/admin/workspace-settings/manage-previews))
* `CREATE CONNECTION` privilege on the Unity Catalog metastore
* A Nimble API key — [sign up](https://online.nimbleway.com/signup) and generate one from [Settings → API Keys](/nimble-sdk/admin/account-management#api-keys)
## Install from Databricks Marketplace
In your Databricks workspace, go to **Marketplace** and search for **Nimble**.
Click **Install**. In the installation dialog, configure:
| Field | Value |
| - | - |
| **Connection name** | A name for the Unity Catalog connection (default: `nimble-mcp-marketplace`) |
| **Host** | Pre-populated |
| **Base path** | Pre-populated |
| **Bearer token** | Your Nimble API key |
Go to **Agents > MCP Servers** tab and confirm `nimble-mcp-marketplace` appears with status **Active**.
## Share the connection
Grant access so team members can use the Nimble MCP server:
1. Go to **Catalog > Connections** and click the Nimble connection.
2. Open the **Permissions** tab and grant **USE CONNECTION** to the principals that need access.
## Governance with Unity AI Gateway
Once installed, the Nimble connection appears automatically in **Unity AI Gateway > MCPs** as an active MCP server — no extra registration step. Unity AI Gateway is the control plane for AI traffic in Databricks, so every Nimble call routes through its managed proxy and inherits Databricks-native governance.
### Available now
* **Managed proxy and AI Playground** — Nimble is reachable at `https:///api/2.0/mcp/external/` and available in the Playground.
* **Audit and usage tracking** — every proxied Nimble call is recorded in the `system.access.audit` system table with the calling user, connection, HTTP method, and status code. The built-in **AI Gateway Usage Analytics** dashboard visualizes this under the **External MCP Server** tab.
Query your own Nimble usage directly:
```sql theme={"system"}
SELECT event_time,
user_identity.email AS user,
request_params.connection_name AS connection_name,
request_params.http_method AS http_method,
response.status_code AS status_code
FROM system.access.audit
WHERE service_name = 'ucHttpConnection'
AND action_name = 'ucHttpConnectionProxiedRequest'
AND request_params.connection_name ILIKE '%nimble%'
ORDER BY event_time DESC;
```
### Databricks MCP governance Beta
Ask your Databricks account team to enable these on your workspace:
* **Payload logging** — full request and response bodies recorded to a Unity Catalog inference table for audit and replay (the audit table above logs metadata only, not bodies).
* **Service policies** — constrain a Nimble tool with a SQL Unity Catalog function (for example, a domain allowlist on `nimble_search`); the gateway enforces the policy before the call leaves Databricks.
* **Rate limits and cost attribution** — meter and attribute Nimble usage per user and workload.
The Marketplace connection authenticates to Nimble with a shared bearer token, so all traffic shares one Nimble identity. Per-user authentication — where each Databricks user authenticates to Nimble individually (on-behalf-of) so the end-user identity reaches Nimble's logs — is on the roadmap.
## Test in Databricks
### AI Playground
Choose a model with the **Tools enabled** label.
Click **Tools > + Add tool > MCP Servers > External MCP servers** and select the `nimble-mcp-marketplace` connection.
Ask the model to search the web, extract a page, or map a site to verify the tools work.
### Databricks Assistant
1. Open **Databricks Assistant** and click the **Settings** icon.
2. Under **MCP Servers**, click **+ Add MCP Server > External MCP servers** and select the `nimble-mcp-marketplace` connection.
Once the connection is verified here, point a production agent at the same proxy URL. The [Nimble MCP + Databricks notebook](https://github.com/Nimbleway/cookbook/blob/main/nimble-mcp-server/databricks-agent/nimble-mcp-databricks-marketplace.ipynb) walks through connecting a Databricks-hosted LLM to Nimble tools with LangGraph and the `DatabricksMCPClient`.
## Resources
Install Nimble MCP Server directly from the Marketplace
Full MCP Server setup for Claude, Cursor, and other clients
Databricks documentation for external MCP server connections
Call Nimble as SQL table functions to enrich Delta tables at scale
# Azure MCP Center
Source: https://docs.nimbleway.com/integrations/partnerships/microsoft/azure-mcp-center
Install Nimble MCP from Azure MCP Center and use it in VS Code with GitHub Copilot Agent Mode
Nimble MCP is an officially onboarded partner on [Azure MCP Center](https://mcp.azure.com/detail/nimble-mcp) — Microsoft's curated registry of enterprise-ready MCP servers, powered by Azure API Center. Install with one click into VS Code, sign in with OAuth, and use Nimble's full web data toolkit in GitHub Copilot Agent Mode. No API key to paste.
## What you get
* **Nimble's web data toolkit** in VS Code Copilot Agent Mode — search, extract, map, crawl, and Nimble Agents
* **One-click install** — no JSON config to paste
* **OAuth 2.1 sign-in** with PKCE — no API key to manage or rotate
* **Streamable HTTP transport** at `https://mcp.nimbleway.com/mcp`
## Prerequisites
* [VS Code](https://code.visualstudio.com/) **1.99 or later** with the [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension
* Copilot Agent Mode enabled (`chat.agent.enabled` in settings)
* A Nimble account — [sign up free](https://online.nimbleway.com/signup)
## Install from Azure MCP Center
Visit [mcp.azure.com/detail/nimble-mcp](https://mcp.azure.com/detail/nimble-mcp).
The button launches VS Code and prompts to add Nimble MCP to your settings.
On first tool call, VS Code prompts to authenticate. Click **Allow**, then approve the requested scopes in the browser — VS Code stores the tokens and refreshes them automatically.
Open **Copilot Chat → Agent Mode**, click the tools icon, and confirm the Nimble tools appear in the list. Ask Copilot to search the web, extract a page, or map a site — Copilot picks the right Nimble tool and runs it.
## Why web tools belong in your IDE
Copilot is great at writing code — but its training cuts off, it doesn't know your dependencies' breaking changes, and it can't see the third-party docs and live data your code actually has to work with. Web search and extraction close that gap across every stage of the dev loop:
* **Research before you build.** Scope new features by searching real usage, competitor implementations, and current benchmarks — not stale snippets.
* **Ground technical planning in current docs.** Pull the latest API surface for the libraries you depend on, not the version Copilot was trained on.
* **Pull real data into your tests.** Extract live pages as clean JSON for fixtures, seed data, or generated types — no copy-paste, no mocks.
* **Build agents that need the web.** Test the same Nimble MCP your shipped agent will use, directly in Copilot, before you commit it.
## Try these prompts
Paste any of these into Copilot Chat in Agent Mode — Copilot picks the right Nimble tool automatically. The flow follows a normal dev loop: research, plan, test, ship.
"I'm scoping a checkout flow. Search recent articles and extract the top 5 — summarize patterns, edge cases, and pitfalls before I write the spec."
"Search for the latest LangGraph streaming API and breaking changes since v0.2, then update `graph.ts` to match."
"Extract these 10 product URLs as clean JSON and save them to `fixtures/products.json` for my integration tests."
"I'm building a research agent in `src/agent.ts`. Use Nimble search and extract live so I can debug real responses, then wire the same MCP into the agent's tool list."
## Use Nimble in your own Azure API Center
Enterprises with their own [Azure API Center](https://learn.microsoft.com/azure/api-center/set-up-api-center) registry get Nimble MCP automatically — every new API Center surfaces the same official partner servers as `mcp.azure.com`. Expose Nimble to your developers alongside your internal MCP servers, optionally proxied through [Azure API Management (AI Gateway)](https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities) for centralized auth, rate limiting, and audit logging.
## Resources
Install Nimble MCP directly into VS Code from the Azure MCP Center listing
Full MCP Server setup for Cursor, Claude, and other clients
Build your own private MCP registry on Azure
Microsoft's docs for installing and using MCP servers in VS Code
# Copilot Studio
Source: https://docs.nimbleway.com/integrations/partnerships/microsoft/copilot-studio
Manually configure the Nimble MCP server as a tool in a Microsoft Copilot Studio agent
Microsoft Copilot Studio agents can call Nimble's web data toolkit — search, extract, map, crawl, and Nimble Agents — through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). This page covers the manual setup: add the Nimble MCP server to an agent using the built-in MCP onboarding wizard.
## What you get
* **Nimble's web data toolkit** in any Copilot Studio agent — search, extract, map, crawl, and Nimble Agents
* **Dynamic tool discovery** — the agent picks up every Nimble MCP tool automatically; new tools appear without reconfiguration
* **OAuth 2.1 sign-in** — authenticate in the browser during setup; no API key to paste, manage, or rotate
* **Streamable HTTP transport** at `https://mcp.nimbleway.com/mcp`
## Prerequisites
* A **Microsoft Copilot Studio** environment with an agent you can edit ([licensing details](https://learn.microsoft.com/en-us/microsoft-copilot-studio/requirements-licensing-subscriptions))
* A **Nimble account** — [sign up free](https://online.nimbleway.com/signup). Sign-in happens through OAuth during setup; no API key to paste.
MCP routes tool results through the agent's context window and can consume tokens
quickly. For high-volume retrieval, consider the [Nimble API](/nimble-sdk/web-tools/search) directly.
## Add Nimble MCP to your agent
The MCP onboarding wizard is the fastest way to connect an existing MCP server. It lives inside Copilot Studio — no trip to Power Apps required.
In Copilot Studio, open your agent and go to the **Tools** page. Select **Add a tool**.
In the **Add tool** dialog, under **Create new**, select **Model Context Protocol**.
Fill in the server fields, then set authentication to **OAuth 2.0** with type **Dynamic discovery**. Nimble MCP supports OAuth 2.0 Dynamic Client Registration (DCR), so it advertises its endpoints automatically — there's nothing else to configure. Select **Create**.
| Field | Value |
| - | - |
| **Server name** | `Nimble MCP` |
| **Server description** | `Nimble turns the live internet into structured, reliable data for AI — search, extract, map, and crawl any website, plus purpose-built Agents for your use case at scale.` |
| **Server URL** | `https://mcp.nimbleway.com/mcp` |
| **Authentication** | `OAuth 2.0` → `Dynamic discovery` |
The agent orchestrator uses the description to decide when to call Nimble — keep it clear and specific.
Copilot Studio creates the custom connector. On the **Add tool** dialog, open the **Connection** dropdown and select **Create new connection**.
A browser window opens to authorize with your Nimble workspace. Review the requested permissions — verify identity, maintain access when offline, search and extract, and manage Agents — and select **Approve**. Copilot Studio stores the tokens and refreshes them automatically.
Back on the **Add tool** dialog, select **Add and configure**. Copilot Studio discovers the Nimble tools and lists them under the agent's tools.
## Verify the connection
Open a test chat with your agent and send a prompt that forces a Nimble tool call:
```text theme={"system"}
Search the web for the latest Microsoft Build 2026 announcements,
then extract the full text of the top result as clean data.
```
The agent calls Nimble **Search**, then **Extract**, and returns results grounded in live web data. If the tools run and return content, the connection works end to end.
Expand the activity map in the test pane to confirm the agent invoked the Nimble tools (`nimble_search` and `nimble_extract`) rather than answering from its own knowledge.
## Resources
Full MCP server setup for Cursor, Claude, and other clients
One-click Nimble MCP install for VS Code with GitHub Copilot
Microsoft's docs for extending agents with MCP servers
Reference for the Search, Extract, Map, and Crawl APIs behind the tools
# Oracle OCI
Source: https://docs.nimbleway.com/integrations/partnerships/oracle/oci
Get started with Nimble on Oracle Cloud Infrastructure: deploy the Managed Enterprise Chat Agent with Nimble web search, or connect Nimble MCP to OCI Generative AI.
Give agents on Oracle Cloud Infrastructure (OCI) live web data from Nimble. There are two ways in, and both use the same Nimble API key.
| Path | Best for | What you configure |
| - | - | - |
| [Managed Enterprise Chat Agent](#deploy-the-managed-enterprise-chat-agent) | A ready-made RAG chat app on OCI, with web search built in | Your Nimble API key in the Resource Manager form, plus network settings |
| [Nimble MCP with OCI Generative AI](#connect-nimble-mcp-to-oci-generative-ai) | Your own agent code calling models hosted on OCI | One `mcp` tool in a Responses API request |
## Prerequisites
* A Nimble API key from the [Nimble dashboard](https://online.nimbleway.com/settings/api-keys). A free trial is available.
* An OCI tenancy.
* For the chat agent: `git`, `python3`, and `zip` on your machine, and rights to create IAM policies in the tenancy. See [Fill in the pack settings](#deploy-the-managed-enterprise-chat-agent) if you lack them.
* For MCP: an OCI Generative AI project, an OCI Generative AI API key, and an IAM policy that grants the key access. The [setup step](#how-it-works) links Oracle's guide.
## Deploy the Managed Enterprise Chat Agent
The [OCI AI Accelerator Starter Packs](https://github.com/oci-ai-incubations/ai-accelerator-starter-packs) are Terraform stacks from Oracle. They deploy complete AI apps onto Oracle Kubernetes Engine (OKE). The **Managed Enterprise Chat Agent** pack (`paas_rag`) is an enterprise chat app. It answers questions over your uploaded documents with citations, using OCI Generative AI models and Oracle 26ai as the vector store. It needs no GPUs.
Nimble is the pack's built-in web search provider. Add your Nimble API key at deploy time and the pack's backend answers web search requests with Nimble.
The Nimble setting was added after Oracle's latest published release. The `v0.0.8_paas_rag.zip` on the Releases page does not include it. Build the stack zip from the repository's `main` branch, as shown below, until a newer release ships.
Clone the repository, generate the schema for the `paas_rag` pack, and zip the Terraform folder:
```bash theme={"system"}
git clone https://github.com/oci-ai-incubations/ai-accelerator-starter-packs.git
cd ai-accelerator-starter-packs
python3 -m venv venv && source venv/bin/activate
pip3 install -r requirements.txt
python3 create_final_schema.py -c paas_rag
zip -r paas_rag.zip ai-accelerator-tf
```
In the OCI Console, go to **Developer Services → Resource Manager → Stacks**. Pick your compartment, click **Create Stack**, select **.zip file**, and upload `paas_rag.zip`. Click **Next**.
The form groups settings by section. These are the ones that matter for this pack:
| Section | Setting | What to enter |
| - | - | - |
| Deployment Configuration | **RAG Deployment Size** | `small` to start. `medium` provisions a larger database (16 ECPU, 8 TB). |
| Deployment Configuration | **OCI GenAI Services Region** | A region where OCI Generative AI is available. It does not have to match the cluster region. Default: `us-chicago-1`. |
| OCI Blueprints Administrator Account | Username, password, email | The login for the OCI AI Blueprints portal, which manages the deployment. The chat app itself has no login. |
| Oracle 26ai Database | **Database Admin Password** | 12+ characters, with uppercase, lowercase, a number, and one of `!@#$%^&*`. |
| Web Search | **Nimbleway API Key** | Your Nimble API key. |
The **Nimbleway API Key** field is optional. Leave it blank and web search is unavailable.
**Create IAM Policies**, under **Advanced Options**, is on by default and writes policies at the tenancy root. That needs tenancy-admin rights. Without them, uncheck it and have an admin apply [Oracle's IAM policies](https://github.com/oci-ai-incubations/ai-accelerator-starter-packs/blob/main/docs/iam-policies.md) first.
By default the stack deploys a public URL, and the API behind it requires no login. Anyone with the URL can run web searches on your Nimble key, use your OCI Generative AI quota, and read uploaded documents. For anything beyond a short test, check **Deploy Private K8s and Load Balancer** under **Advanced Options**, or [restrict ingress by IP](https://github.com/oci-ai-incubations/ai-accelerator-starter-packs/blob/main/docs/limiting_ips_for_public_ingress.md).
Click **Next → Run apply → Create**. Deployment takes roughly 20 to 40 minutes. When it finishes, the stack's **Outputs** tab shows the chat app's address as **RAG Service URL**.
To stop all charges later, open the stack and click **Destroy**.
### What the Nimble setting does
The pack runs [OGX](/integrations/connectors/ogx), the open agentic API server, as its backend. Your API key is passed to OGX's `remote::nimble-search` provider, which is the pack's only web search backend. Any `web_search` tool call the backend handles goes to Nimble [Search](/nimble-sdk/web-tools/search).
The stack ships with this provider configuration:
```yaml theme={"system"}
tool_runtime:
- provider_id: nimble-search
provider_type: remote::nimble-search
config:
api_key: ${env.NIMBLEWAY_API_KEY:=}
max_results: 3
search_depth: lite
```
`max_results: 3` and `search_depth: lite` keep searches fast by default: titles, URLs, and snippets for the top three results. To change them, edit `ai-accelerator-tf/files/llamastack_paas_config.yaml` before you build the zip. See the [OGX page](/integrations/connectors/ogx#configuration) for every option.
Terraform users can set the key as the `nimbleway_api_key` variable instead of using the console form. It is marked sensitive, so Terraform does not print it.
## Connect Nimble MCP to OCI Generative AI
OCI Generative AI serves an OpenAI-compatible Responses API. It supports [MCP calling](https://docs.oracle.com/en-us/iaas/Content/generative-ai/mcp.htm): OCI connects to a remote MCP server directly, with no tool-calling loop in your code. Point it at Nimble's hosted MCP server and OCI models that support the Responses API can search and extract the web.
Set three environment variables before you run the example:
```bash theme={"system"}
export OCI_GENAI_API_KEY="your-oci-genai-api-key"
export OCI_GENAI_PROJECT_OCID="ocid1.generativeaiproject.oc1.us-chicago-1.xxxx"
export NIMBLE_API_KEY="your-nimble-api-key"
```
### Example Request
```python Python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1",
api_key=os.environ["OCI_GENAI_API_KEY"],
project=os.environ["OCI_GENAI_PROJECT_OCID"],
)
response = client.responses.create(
model="openai.gpt-oss-120b",
tools=[
{
"type": "mcp",
"server_label": "nimble",
"server_description": "Live web search and page extraction.",
"server_url": "https://mcp.nimbleway.com/mcp",
"authorization": os.environ["NIMBLE_API_KEY"],
"allowed_tools": ["nimble_search", "nimble_extract"],
"require_approval": "never",
}
],
input="What did Oracle announce this week? Search the web and cite sources.",
)
print(response.output_text)
```
```typescript TypeScript theme={"system"}
import OpenAI from "openai";
// Fail fast: an unset key would make the SDK fall back to OPENAI_API_KEY.
function env(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} first`);
return value;
}
const client = new OpenAI({
baseURL: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1",
apiKey: env("OCI_GENAI_API_KEY"),
project: env("OCI_GENAI_PROJECT_OCID"),
});
const response = await client.responses.create({
model: "openai.gpt-oss-120b",
tools: [
{
type: "mcp",
server_label: "nimble",
server_description: "Live web search and page extraction.",
server_url: "https://mcp.nimbleway.com/mcp",
authorization: env("NIMBLE_API_KEY"),
allowed_tools: ["nimble_search", "nimble_extract"],
require_approval: "never",
},
],
input: "What did Oracle announce this week? Search the web and cite sources.",
});
console.log(response.output_text);
```
```bash cURL theme={"system"}
curl https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1/responses \
-H "Authorization: Bearer $OCI_GENAI_API_KEY" \
-H "OpenAI-Project: $OCI_GENAI_PROJECT_OCID" \
-H "Content-Type: application/json" \
-d '{
"model": "openai.gpt-oss-120b",
"tools": [{
"type": "mcp",
"server_label": "nimble",
"server_description": "Live web search and page extraction.",
"server_url": "https://mcp.nimbleway.com/mcp",
"authorization": "'"$NIMBLE_API_KEY"'",
"allowed_tools": ["nimble_search", "nimble_extract"],
"require_approval": "never"
}],
"input": "What did Oracle announce this week? Search the web and cite sources."
}'
```
Any OpenAI-compatible client works the same way. The TypeScript example uses top-level `await`, so run it as an ES module.
### How it works
In the OCI Console, create a Generative AI **project** and an **API key** in the same region. Grant the key permission with an IAM policy. Oracle's [quick start](https://docs.oracle.com/en-us/iaas/Content/generative-ai/get-started-agents.htm) walks through all three.
Set `server_url` to `https://mcp.nimbleway.com/mcp` and put your Nimble API key in `authorization`. Pass the raw key, with no `Bearer` prefix.
When the model decides it needs the web, OCI calls the Nimble tool directly and feeds the results back to the model. Your code receives the final, grounded answer.
### Parameters
`https://inference.generativeai..oci.oraclecloud.com/openai/v1`. Use the region where your project and API key live, for example `us-chicago-1` or `us-ashburn-1`.
**Required.** The OCID of your OCI Generative AI project, which starts with `ocid1.generativeaiproject`. The OpenAI SDK sends it as the `OpenAI-Project` header.
**Required.** Your Nimble API key, with no `Bearer` prefix. Oracle states that OCI does not store or log MCP authorization tokens.
**Optional.** Limits which Nimble tools the model sees. Fewer tools means a shorter prompt, lower cost, and faster responses. Keep it set. Without it, the model also sees tools that start paid crawls or delete Agents. With `require_approval: never`, those run without confirmation. See the [MCP server docs](/integrations/mcp-server/mcp-server#tools) for what each product covers.
**Optional.** Set to `never` so OCI runs Nimble tools without pausing for approval.
**Required.** Any OCI Generative AI model that supports the Responses API, such as `openai.gpt-oss-120b`. Meta models do not support the Responses API.
## Resources
The agentic API server behind the chat agent, and its Nimble provider settings.
Tools, authentication, and setup for other MCP clients.
Oracle's Terraform stacks, including the Managed Enterprise Chat Agent.
Oracle's reference for remote MCP tools in the Responses API.
# Snowflake
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake
Bring Nimble's live web tools into Snowflake's Cortex AI stack. Search, extract, map, crawl, and Nimble Agents run alongside warehouse data.
Nimble brings the live web into Snowflake's data and AI stack. Search, page extraction, site maps, crawls, and Nimble Agents sit alongside your warehouse tables, semantic models, and Cortex skills, so the AI you build inside Snowflake can ground its answers in current external signal, not just historical data.
## Why web data belongs next to your warehouse
Snowflake's AI surfaces (Cortex Code, Cortex Analyst, Cortex Agents, Streamlit) are great at orchestrating the data you already own. But the world your data describes lives outside the warehouse, and most of the questions stakeholders actually ask need both. Nimble closes that gap without leaving the Snowflake ecosystem:
* **Enrich tables in place.** Pull current company metadata, product details, or pricing for the rows in a Snowflake table, and write the results straight back as a staging table.
* **Validate semantic models against the real world.** Check whether the entities a Cortex Analyst semantic model points at still exist, still resolve, still match.
* **Ground Streamlit dashboards in live signal.** Combine warehouse aggregates with current web context (competitor pricing, news mentions, regulatory updates) in the same view.
* **Build Cortex Agents that need the web.** Give an agent the same Nimble tools you'd use yourself, so it can research, verify, and gather external context as part of its workflow.
## Pick a surface
Install Nimble MCP in Snowflake's native AI coding agent. One `cortex mcp add` command, OAuth sign-in, full web data toolkit in every Cortex Code session.
Deploy `NIMBLE_SEARCH` and `NIMBLE_EXTRACT` as scalar Snowflake UDFs callable inline from any `SELECT`, then wire them into a pre-built Cortex Agent for Snowflake Intelligence.
Enrich warehouse tables at scale with `NIMBLE_AGENT_RUN`, a SQL-native UDTF for Snowflake. Lateral-join any table with typed structured output from Nimble Extract Templates — dbt-native, Task-native, no LLM in the loop.
Install Nimble's Search and Agents apps from the Snowflake Marketplace and call live web search, AI-synthesized answers, and site-specific extraction from SQL. One-click install, your key in a Snowflake Secret you control.
## Resources
Full MCP Server setup for Cursor, Claude, and other clients
Snowflake's docs for the native AI coding agent
Overview of the Cortex Agents runtime and orchestration model
How Snowflake gates outbound HTTPS from UDFs and stored procedures
# App Builder Skills
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake/app-builder-skills
Nimble's agentic App Builder Skills for Snowflake. Each turns a short brief into a working in-tenant data product: a ready-to-use Streamlit cockpit, a Cortex agent, and live Nimble web data. Run them from Cortex Code, or attach them to a Cortex Agent for Snowflake CoWork.
Nimble ships a family of **App Builder Skills**: packaged [Snowflake Agent Skills](https://docs.snowflake.com/en/user-guide/cortex-code/extensibility) that take a short brief and provision a complete, in-tenant data product. One brief stands up a ready-to-use Streamlit cockpit, a Cortex agent, and live Nimble web data, end to end, without leaving the Snowflake account.
Because they're standard Snowflake Agent Skills, the same skill runs from either agentic surface:
* **[Cortex Code](/integrations/partnerships/snowflake/cortex-code) (CoCo).** Load the skill into the CLI and invoke it in a session.
* **[Snowflake CoWork](https://www.snowflake.com/en/product/snowflake-cowork/) (formerly Snowflake Intelligence).** Attach the skill to a Cortex Agent and chat with it.
The [Cortex Code MCP install](/integrations/partnerships/snowflake/cortex-code) puts Nimble's toolkit into a session and [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) expose Search and Extract as SQL. App Builder Skills go further and *build*: they stand up the schema, the scheduled ingestion, the views, the agent, and the cockpit for you.
Every skill is config-driven and self-contained: the app it builds stays updatable after creation (add a keyword and the next refresh picks it up), and nothing leaves your Snowflake account.
## Prerequisites
All App Builder Skills share one foundation:
* The **Nimble × Snowflake integration**: the External Access Integration, the API-key secret, the `NIMBLE_AGENT_RUN` UDTF, and the `NIMBLE_SEARCH` UDF. See [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) for the setup. A skill verifies these at runtime and can guided-install them, but only with explicit consent and your Nimble API key, run as `ACCOUNTADMIN`.
* **Cortex Agents enabled** on the account, and a role that can create schemas in the target database.
## Use a skill
### From Cortex Code (CLI)
The quickest path is `/skill add` with the skill's folder URL from the cookbook, not the whole repo:
```bash theme={"system"}
# In a Cortex Code session
/skill add https://github.com/Nimbleway/cookbook/tree/main/snowflake/coco-skills/cmo-intelligence
/skill list
```
Then reference the skill by name in conversation. For example: `$cmo-intelligence build a digital-shelf app for the coffee category`.
### As a Cortex Agent (Snowflake CoWork)
Stand up a **Nimble App Builder** agent once, then anyone can build apps by chatting with it in [Snowflake CoWork](https://www.snowflake.com/en/product/snowflake-cowork/) (formerly Snowflake Intelligence), no CLI required. The agent loads its skills straight from the cookbook Git repository, so updates are a `FETCH` away. Run the quick-start as `ACCOUNTADMIN`.
Create a Git API integration scoped to the Nimble org, register the cookbook repository, and fetch it. The agent reads its skill files from here.
```sql theme={"system"}
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE API INTEGRATION NIMBLE_COOKBOOK_GIT_API
API_PROVIDER = git_https_api
API_ALLOWED_PREFIXES = ('https://github.com/Nimbleway/')
ENABLED = TRUE;
CREATE OR REPLACE GIT REPOSITORY NIMBLE_INTEGRATION.TOOLS.NIMBLE_COOKBOOK
ORIGIN = 'https://github.com/Nimbleway/cookbook.git'
API_INTEGRATION = NIMBLE_COOKBOOK_GIT_API;
ALTER GIT REPOSITORY NIMBLE_INTEGRATION.TOOLS.NIMBLE_COOKBOOK FETCH;
```
The agent provisions apps by running SQL: DDL, DML, `CALL`, `EXECUTE TASK`, `GRANT`. This MCP server gives it that capability, scoped to the integration's warehouse.
```sql theme={"system"}
CREATE OR REPLACE MCP SERVER NIMBLE_INTEGRATION.TOOLS.SQL_EXEC_SERVER
FROM SPECIFICATION $$
tools:
- title: "SQL Execution Tool"
name: "sql_exec_tool"
type: "SYSTEM_EXECUTE_SQL"
description: "Execute any SQL statement against Snowflake — DDL, DML, queries, CALL procedures, EXECUTE TASK, GRANT, and more."
config:
read_only: false
query_timeout: 600
warehouse: "NIMBLE_AGENT_WH"
$$;
```
Attach one or more skills from the Git repository. **Each skill defines a different app type, and the agent routes a request to the matching one.** Add a skill by listing another entry under `skills:`.
```sql theme={"system"}
CREATE OR REPLACE AGENT NIMBLE_INTEGRATION.TOOLS.NIMBLE_APP_BUILDER
PROFILE = '{"display_name":"Nimble App Builder","avatar":"SparklesAgentIcon"}'
COMMENT = 'Builds and provisions complete Snowflake apps powered by live Nimble web data.'
FROM SPECIFICATION
$$
models:
orchestration: auto
instructions:
orchestration: |
You are the Nimble App Builder — you build and provision complete Snowflake apps powered by live web data from Nimble.
You have one or more skills attached. Each skill defines a different type of app you can build (e.g. CMO digital-shelf intelligence, competitive pricing, etc.). When the user asks you to build something, identify which skill matches their request and follow that skill's instructions precisely.
General rules across all skills:
- Always confirm the user's intent and parameters before creating anything.
- Use EXECUTE TASK for long-running operations — never block the session with a synchronous CALL.
- One app = one schema. Check for existing apps before creating to avoid clobbering live data.
- Substitute all template placeholders before running SQL.
- End every successful build by delivering the key access points (URLs, agent names, dashboards).
If the user's request doesn't match any attached skill, say so clearly and describe what you can build today.
response: |
Be concise and action-oriented. Lead with what you did or need. Show proposals as formatted lists for easy review. After building, deliver access points clearly.
sample_questions:
- question: Build a digital-shelf app for the coffee category, focal brand Nespresso
- question: Set up CMO intelligence for Pampers in diapers
- question: What kinds of apps can you build?
- question: Monitor the chocolate category on Walmart, Amazon, and Target
tools:
- tool_spec:
type: web_search
name: web_search
skills:
- name: cmo-intelligence
source:
type: GIT
path: "@NIMBLE_INTEGRATION.TOOLS.NIMBLE_COOKBOOK/branches/main/snowflake/coco-skills/cmo-intelligence"
tool_resources:
web_search:
max_results: 10
mcp_servers:
- server_spec:
name: "NIMBLE_INTEGRATION.TOOLS.SQL_EXEC_SERVER"
$$;
```
Confirm the agent registered:
```sql theme={"system"}
DESCRIBE AGENT NIMBLE_INTEGRATION.TOOLS.NIMBLE_APP_BUILDER;
```
Then open Snowflake CoWork, pick **Nimble App Builder**, and describe the app you want.
Pulled a newer version of a skill? Re-run `ALTER GIT REPOSITORY NIMBLE_INTEGRATION.TOOLS.NIMBLE_COOKBOOK FETCH;` to refresh the repository, then `CREATE OR REPLACE AGENT …` to pick it up.
## Skills gallery
Each skill is a different app type you can install and run from either surface. More are on the way; here's what's available today.
### CMO Intelligence
Name a category (a brand is optional), and the **CMO Intelligence** skill turns the brief into a working digital-shelf intelligence app covering share of shelf, pricing, content health, and sentiment, with live Nimble web data, a Cortex agent, and a ready-to-use Streamlit cockpit.
**What it builds.** One brief stands up a complete, self-contained app in its own schema:
* **A per-app schema.** Each provision targets its own `.`, so apps never collide and a live app is never clobbered.
* **Config tables as the source of truth.** `CFG_APP` and `CFG_QUERIES` hold the brand, keywords, retailers, and geography; the views and the scheduled Task read from them, so the app stays updatable after creation.
* **Scheduled web-data ingestion.** A `DAILY_SHELF_TASK` calls `REFRESH_SHELF()`, which pulls search and product-page data through the `NIMBLE_AGENT_RUN` UDTF and lands it in raw tables: SERP via a lateral join, the high-volume PDP fan-out concurrently.
* **A Cortex brand resolver.** `REBUILD_BRAND_MAP()` normalizes raw product titles into a `BRAND_MAP` using a fast Cortex model, so share-of-shelf and focal-brand tagging stay accurate. It refreshes on every run.
* **Analytics views.** Share of shelf, content health, out-of-stock alerts, daily trends, sentiment summary, share of AI answer, and next-best-actions.
* **A Cortex Analyst semantic view.** `SHELF_SV` exposes the analytics layer for natural-language questions.
* **A Cortex agent.** `_SHELF_ANALYST` answers shelf and price questions over `SHELF_SV`, and carries a live-web `NIMBLE_SEARCH` tool for fresh context mid-conversation.
* **A ready-to-use Streamlit cockpit.** A per-app Streamlit-in-Snowflake app (named per app, e.g. `_CMO_COCKPIT`) with pricing, digital shelf, and sentiment surfaces, ready on first open.
**Ask for an app.** Describe the goal. A category is required; a focal brand is optional. In Cortex Code, prefix with `$cmo-intelligence`; in CoWork, just ask the Nimble App Builder agent:
* `set up CMO intelligence for the chocolate category, focal brand Acme`
* `build a digital-shelf app for the coffee category`
* `monitor our category on Walmart, Amazon, and Target`
The skill confirms the details with you, then provisions the app and returns the cockpit URL, the agent name, and a headline takeaway (such as the focal brand's share of shelf).
**How it works.**
1. **Preflight:** verifies the integration's functions exist (`NIMBLE_AGENT_RUN` + `NIMBLE_SEARCH`), resolves the available Cortex models, confirms the target database and warehouse, and checks for an existing app in the target schema.
2. **Intake:** the "category architect" proposes the focal brand, \~6 keywords, focal-brand patterns, and a schema name from the category, then shows the proposal for confirmation. Defaults: Walmart / Amazon / Target, US geography, daily refresh.
3. **Provision:** runs the SQL templates in order, creating every object above, from the config tables through the `_SHELF_ANALYST` Cortex agent.
4. **Seed:** fires `DAILY_SHELF_TASK` server-side for the first snapshot (focal-first, so the cockpit has real content fast), then deploys the Streamlit cockpit to a per-app stage.
5. **Verify and deliver:** confirms the full object set exists and the views are populated, then returns the cockpit URL, the agent, and the headline insight.
The CMO Intelligence skill: SKILL.md, the SQL templates, the cockpit, and the bundled integration
## Resources
Every Nimble App Builder Skill, with SKILL.md, SQL templates, and install guides
Install the Nimble MCP server in Cortex Code for ad-hoc web data in any session
The `NIMBLE_SEARCH` / `NIMBLE_EXTRACT` UDFs and the integration these skills build on
Snowflake's guide to creating, installing, and invoking custom Cortex Code skills
# Cortex Agents
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake/cortex-agents
Call Nimble Search and Extract from Snowflake SQL, plus a pre-built Cortex Agent, without leaving your account
Bring Nimble's web data into Snowflake as scalar SQL UDFs, then wire them into a [Cortex Agent](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents) so any Snowflake Intelligence chat (or any caller of the agent run REST API) can search the web and pull live page content alongside your warehouse data.
## What you get
* **`NIMBLE_SEARCH` and `NIMBLE_EXTRACT` callable inline from any Snowflake SQL.** Pipelines, notebooks, Streamlit, dbt — usable directly in `SELECT`, views, and joins because the UDFs return `VARIANT`.
* **A pre-built Cortex Agent** wired to those tools, invocable from Snowflake Intelligence or the agent run REST API
* **Stays inside your Snowflake account.** Only authorized outbound calls hit Nimble.
* **One-time setup, run by ACCOUNTADMIN.** Under 5 minutes.
## Prerequisites
* `ACCOUNTADMIN` role
* Snowflake Enterprise edition or higher
* A Nimble API key ([sign up free](https://online.nimbleway.com/signup))
## Install Nimble in Snowflake
Set up an isolated namespace for the integration. A dedicated warehouse keeps agent traffic separate from analytics workloads and makes cost attribution trivial.
```sql theme={"system"}
USE ROLE ACCOUNTADMIN;
CREATE ROLE IF NOT EXISTS nimble_role;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE nimble_role;
CREATE DATABASE IF NOT EXISTS NIMBLE_INTEGRATION;
CREATE SCHEMA IF NOT EXISTS NIMBLE_INTEGRATION.TOOLS;
GRANT USAGE ON DATABASE NIMBLE_INTEGRATION TO ROLE nimble_role;
GRANT USAGE ON SCHEMA NIMBLE_INTEGRATION.TOOLS TO ROLE nimble_role;
CREATE WAREHOUSE IF NOT EXISTS NIMBLE_AGENT_WH
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE;
GRANT USAGE ON WAREHOUSE NIMBLE_AGENT_WH TO ROLE nimble_role;
```
Full setup script: role, grants, database, schema, warehouse
Create a network rule, store your Nimble API key as a Snowflake secret, and bind both into an External Access Integration. The UDFs reference the EAI to make outbound HTTPS calls.
```sql theme={"system"}
CREATE OR REPLACE NETWORK RULE nimble_api_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('sdk.nimbleway.com:443');
CREATE OR REPLACE SECRET nimble_api_key
TYPE = GENERIC_STRING
SECRET_STRING = 'YOUR_NIMBLE_API_KEY';
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION nimble_eai
ALLOWED_NETWORK_RULES = (nimble_api_rule)
ALLOWED_AUTHENTICATION_SECRETS = (nimble_api_key)
ENABLED = TRUE;
GRANT USAGE ON INTEGRATION nimble_eai TO ROLE nimble_role;
```
Network rule, secret, and External Access Integration block
A Python UDF that calls Nimble's Search API and returns the response as a `VARIANT`. Callers navigate the JSON inline with `:field` syntax — no `PARSE_JSON()` needed.
```sql theme={"system"}
CREATE OR REPLACE FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH(
query STRING,
max_results INTEGER DEFAULT 10
)
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.11
PACKAGES = ('requests')
HANDLER = 'run'
EXTERNAL_ACCESS_INTEGRATIONS = (nimble_eai)
SECRETS = ('api_key' = nimble_api_key)
AS $$
import _snowflake, requests
def run(query, max_results):
key = _snowflake.get_generic_secret_string('api_key')
r = requests.post(
'https://sdk.nimbleway.com/v2/search',
headers={'Authorization': f'Bearer {key}',
'Content-Type': 'application/json'},
json={'query': query, 'max_results': max_results},
timeout=60,
)
r.raise_for_status()
return r.json()
$$;
GRANT USAGE ON FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH(STRING, INTEGER)
TO ROLE nimble_role;
```
Full UDF with focus, depth, country, locale, and domain filters
Same shape as `NIMBLE_SEARCH`. Takes a single URL and returns the parsed page content as a `VARIANT`.
```sql theme={"system"}
CREATE OR REPLACE FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_EXTRACT(
url STRING,
render BOOLEAN DEFAULT TRUE
)
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.11
PACKAGES = ('requests')
HANDLER = 'run'
EXTERNAL_ACCESS_INTEGRATIONS = (nimble_eai)
SECRETS = ('api_key' = nimble_api_key)
AS $$
import _snowflake, requests
def run(url, render):
key = _snowflake.get_generic_secret_string('api_key')
r = requests.post(
'https://sdk.nimbleway.com/v2/extract',
headers={'Authorization': f'Bearer {key}',
'Content-Type': 'application/json'},
json={'url': url, 'render': render, 'formats': ['markdown']},
timeout=90,
)
r.raise_for_status()
return r.json()
$$;
GRANT USAGE ON FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_EXTRACT(STRING, BOOLEAN)
TO ROLE nimble_role;
```
Full UDF with driver, country, locale, and format selection
Wire both UDFs into a Cortex Agent. `tool_spec.type: generic` declares each tool's JSON schema; `tool_resources` maps the schema to the underlying function (`type: function`) and the warehouse that runs it.
```sql theme={"system"}
CREATE OR REPLACE AGENT nimble_web_research_agent
COMMENT = 'Agent with Nimble web search and content retrieval'
FROM SPECIFICATION
$$
models:
orchestration: auto
orchestration:
budget:
seconds: 60
tokens: 32000
instructions:
system: |
You are a research assistant with access to Nimble's web data toolkit.
Always cite source URLs.
orchestration: |
- Use NIMBLE_SEARCH for queries requiring web search.
- Use NIMBLE_EXTRACT when the user provides specific URLs.
tools:
- tool_spec:
type: generic
name: nimble_search
description: Search the web using Nimble.
input_schema:
type: object
properties:
query: { type: string, description: The search query. }
max_results: { type: integer, description: "Number of results (default 10)." }
required: [query]
- tool_spec:
type: generic
name: nimble_extract
description: Retrieve parsed content for a single URL.
input_schema:
type: object
properties:
url: { type: string, description: The URL to extract. }
render: { type: boolean, description: "JS-render the page (slower; needed for SPAs)." }
required: [url]
tool_resources:
nimble_search:
type: function
execution_environment:
type: warehouse
warehouse: NIMBLE_AGENT_WH
identifier: NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH
nimble_extract:
type: function
execution_environment:
type: warehouse
warehouse: NIMBLE_AGENT_WH
identifier: NIMBLE_INTEGRATION.TOOLS.NIMBLE_EXTRACT
$$;
```
Full agent spec, including grants and verification queries
## Try these recipes
### Recipe 1: Quick search and extract from SQL
Both UDFs return `VARIANT`. Navigate the response inline with `:field` syntax — no `CALL`, no `PARSE_JSON`, no `RESULT_SCAN`.
```sql theme={"system"}
-- Top result URL for a query, in one line
SELECT NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH('AI agents news', 5)
:results[0]:url::STRING AS top_url;
-- Rendered markdown of a single page
SELECT NIMBLE_INTEGRATION.TOOLS.NIMBLE_EXTRACT(
'https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents'
):data:markdown::STRING AS page_markdown;
-- Flatten search results into a table — one row per result
SELECT
f.value:metadata:position::INTEGER AS position,
f.value:title::STRING AS title,
f.value:url::STRING AS url,
f.value:description::STRING AS description,
SUBSTRING(f.value:content::STRING, 1, 300) AS content_preview
FROM TABLE(FLATTEN(
input => NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH('AI agents news', 5):results
)) f
ORDER BY position;
-- Compose with warehouse data: enrich a table of search terms with their top result
SELECT t.term,
NIMBLE_INTEGRATION.TOOLS.NIMBLE_SEARCH(t.term, 3):results[0]:url::STRING AS top_url
FROM my_search_terms t;
```
Because the UDFs are scalar and return `VARIANT`, they slot directly into views, dbt models, `FLATTEN` calls, and joins against warehouse data — no two-step `CALL` + `RESULT_SCAN` + `PARSE_JSON` workaround.
### Recipe 2: CPG retailer price and availability monitoring
A CPG brand keeps its product master in Snowflake and wants daily competitive intelligence on how its SKUs appear across Amazon, Walmart, and Target. Nimble Search finds the listing URL per SKU per retailer; Nimble Extract pulls price, stock, and reviews from each listing; the enriched rows land in a `PRODUCT_LISTINGS` table that BI can read.
**Input: `PRODUCTS`**
| sku | brand | product\_name | upc | category |
| - | - | - | - | - |
| LB-001 | Liquid Death | Mountain Water 16.9oz 12pk | 810014710013 | Beverages |
| OB-014 | Olipop | Vintage Cola 12oz 12pk | 850000334038 | Beverages |
| ATH-002 | Athletic Brewing | Free Wave Hazy IPA 12pk | 850001234567 | Beverages |
**Output: `PRODUCT_LISTINGS`**
| sku | retailer | listing\_url | price | currency | in\_stock | rating | review\_count | last\_seen\_at |
| - | - | - | - | - | - | - | - | - |
| LB-001 | amazon | [https://amazon.com/dp/](https://amazon.com/dp/)... | 18.99 | USD | TRUE | 4.7 | 12483 | 2026-05-25 |
| LB-001 | walmart | [https://walmart.com/ip/](https://walmart.com/ip/)... | 17.48 | USD | TRUE | 4.6 | 3201 | 2026-05-25 |
| LB-001 | target | [https://target.com/p/A-](https://target.com/p/A-)... | 17.99 | USD | FALSE | 4.8 | 942 | 2026-05-25 |
A `v_price_alerts` view layers on top, surfacing SKUs with a 10%+ price drop versus the trailing seven-day median (or any retailer flipping to out-of-stock), and feeds the daily competitive briefing.
Full recipe: sample data, enrichment SQL, the alerts view, and the daily task
### Recipe 3: Chat with the agent in Snowflake Intelligence
The fastest way to try the agent is the [Snowflake Intelligence](https://docs.snowflake.com/en/user-guide/snowflake-cortex/snowflake-intelligence) UI:
From Snowsight, open **AI & ML → Snowflake Intelligence**.
Select **NIMBLE\_WEB\_RESEARCH\_AGENT** from the agent picker. The two tools (`nimble_search`, `nimble_extract`) appear in the tool tray.
Try a prompt that forces both tools. For example: *"Find the three most recent posts on Snowflake's engineering blog about Cortex, then pull the full text of each."* The agent calls `nimble_search`, picks URLs from the results, then calls `nimble_extract` to retrieve page content, and answers with citations.
Need to call the agent programmatically? The same agent is reachable via the [Cortex Agents REST API](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-rest-api): `POST` to `/api/v2/databases/{db}/schemas/{schema}/agents/NIMBLE_WEB_RESEARCH_AGENT:run` with a message body. Useful for Streamlit apps, scheduled tasks, or wiring the agent into an external orchestrator.
### Recipe 4: Schedule recurring enrichment
Wrap the CPG enrichment in a Snowflake task so it runs every morning before the business day. The task uses the same `NIMBLE_AGENT_WH` warehouse and writes incrementally to `PRODUCT_LISTINGS`.
```sql theme={"system"}
CREATE OR REPLACE TASK refresh_product_listings
WAREHOUSE = NIMBLE_AGENT_WH
SCHEDULE = 'USING CRON 0 6 * * * UTC'
AS
CALL NIMBLE_INTEGRATION.RECIPES.REFRESH_PRODUCT_LISTINGS();
ALTER TASK refresh_product_listings RESUME;
```
Full scheduled task: incremental load, retry policy, and dead-letter handling
## Roll out across your organization
* **Grant scoped access.** Grant `nimble_role` to the specific user roles or service accounts that should call the UDFs, not to `PUBLIC`. The role already carries `SNOWFLAKE.CORTEX_USER`, so grantees can invoke the agent without any extra Cortex grant.
* **Tune for your Nimble rate-limit tier.** The cookbook enrichment proc accepts a `max_workers` parameter for concurrent extraction. Start at 4 and raise it as your tier allows; Nimble's [rate-limits page](/nimble-sdk/admin/rate-limits) lists per-tier ceilings.
* **Right-size the warehouse.** `XSMALL` with 60-second auto-suspend is fine for interactive agent chat. For large batch enrichment (thousands of SKUs), step up to `SMALL` or `MEDIUM` only for the duration of the scheduled task; suspend cost is negligible at this size.
* **Contain cost during dev.** Use `TABLESAMPLE` on the source table when iterating on enrichment logic, and monitor spend via the `TASK_HISTORY` and `WAREHOUSE_METERING_HISTORY` views.
* **Pre-approve the agent in shared workspaces.** When sharing Snowflake Intelligence workspaces, add `NIMBLE_WEB_RESEARCH_AGENT` to the workspace's allowed agent list so collaborators can invoke it without re-granting access.
## Resources
Every SQL file referenced on this page, plus the CPG recipe
Request shape, parameters, and response schema for `NIMBLE_SEARCH`
Request shape, parameters, and response schema for `NIMBLE_EXTRACT`
How Snowflake gates outbound HTTPS from UDFs and stored procedures
Overview of the Cortex Agents runtime and orchestration model
Full SQL reference for the agent spec used in Step 5
# Cortex Code
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake/cortex-code
Install Nimble MCP in Snowflake's native AI coding agent. One command, OAuth sign-in, full web data toolkit in every Cortex Code session.
[Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code), Snowflake's native AI coding agent for the terminal, is the first surface for connecting Nimble to Snowflake. One `cortex mcp add` command and an OAuth sign-in put Nimble's full toolkit into every Cortex Code session.
## What you get
* **Nimble's web data toolkit** in Cortex Code, exposed as `mcp__nimble__*` tools
* **One-command install.** No JSON to edit by hand.
* **OAuth 2.1 sign-in with PKCE.** No API key to paste, manage, or rotate. Tokens are stored in your OS keychain and refreshed automatically.
* **Streamable HTTP transport** at `https://mcp.nimbleway.com/mcp`
## Prerequisites
* [Snowflake Cortex Code CLI](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code-cli) installed and on your `PATH`
* A valid Snowflake connection in `~/.snowflake/connections.toml` (run `cortex` once to set this up)
* A Nimble account ([sign up free](https://online.nimbleway.com/signup))
## Install Nimble MCP in Cortex Code
Run a single command from your terminal:
```bash theme={"system"}
cortex mcp add nimble https://mcp.nimbleway.com/mcp --transport http
```
Cortex Code writes the server to `~/.snowflake/cortex/mcp.json` under the `mcpServers` key. No `Authorization` header is needed. Nimble's MCP server advertises OAuth and Cortex Code handles the flow on first connection.
On the first connection, Cortex Code opens your system browser to authenticate. Approve the requested scopes; Cortex Code stores the access and refresh tokens in your OS keychain (`mcp_oauth_nimble`) and refreshes them automatically before expiry.
List configured servers and trigger a connect:
```bash theme={"system"}
cortex mcp list
cortex mcp start
```
`cortex mcp start` reports connected and failed counts and waits up to 300 seconds for each server to load. Once `nimble` shows as connected, its tools are namespaced as `mcp__nimble__search`, `mcp__nimble__extract`, `mcp__nimble__map`, `mcp__nimble__crawl`, and so on.
Start a session with `cortex` and ask the agent for anything that needs the live web. Cortex Code picks the right Nimble tool and runs it. No need to name the tool explicitly.
## Try these prompts
Paste any of these into a Cortex Code session. The agent picks the right Nimble tool and the right Snowflake skill, and chains them together.
"For each domain in `ANALYTICS.CUSTOMERS.COMPANY_DOMAIN`, use Nimble to extract the company's current employee count and headquarters. Write results to `ANALYTICS.STAGING.COMPANY_ENRICHMENT`."
"Check each product URL in `PRODUCT_CATALOG.URL` with Nimble Extract, flagging rows where the product is no longer available and writing them to `DATA_QUALITY.STALE_PRODUCTS`."
"Build a Streamlit app on `SALES_MART.REVENUE` that shows our top 10 accounts and uses Nimble Search to surface recent news for each one in a side panel."
"I'm scoping a competitive-research Cortex Agent. Use Nimble search and extract here so I can iterate on prompts and tool outputs, then turn the working chain into a Cortex Agent definition."
## Roll out Nimble across your organization
Cortex Code admins can enforce Nimble MCP for an entire Snowflake account through Cortex Code's [managed settings](https://docs.snowflake.com/en/user-guide/cortex-code/managed-settings). Add Nimble to the administrator-enforced `mcpServers` block (and optionally the URL allowlist), so every developer using Cortex Code gets the same connection without copy-pasting a command, and unsanctioned MCP servers are filtered out at merge time.
```json theme={"system"}
{
"mcpServers": {
"nimble": {
"type": "http",
"url": "https://mcp.nimbleway.com/mcp"
}
}
}
```
Pair this with `permissions.json` to pre-approve safe Nimble tools (`mcp__nimble__search`, `mcp__nimble__extract`) and require confirmation on long-running ones (`mcp__nimble__crawl`).
## Resources
Full MCP Server setup for Cursor, Claude, and other clients
Snowflake's docs for installing and using the Cortex Code CLI
Transport types, configuration schema, and credential handling
Enforce Nimble MCP across your Snowflake organization
# Data Enrichment
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake/data-enrichment
Enrich warehouse tables at scale with Nimble Extract Templates using a SQL-native UDTF for Snowflake.
This integration is ideal for data engineers who need to enrich large datasets with web intelligence directly in their Snowflake pipelines — without leaving SQL or building custom API integrations.
Nimble provides a SQL-native User Defined Table Function (UDTF) for Snowflake that runs structured web extraction directly inside your queries. The integration uses Snowflake's [External Access Integration](https://docs.snowflake.com/en/developer-guide/external-network-access/external-network-access-overview) to securely connect to the Nimble API, and invokes one [template](/nimble-sdk/web-tools/extract/template) per row — every template returns typed, domain-specific fields server-side, so no LLM is needed downstream to extract structured data from raw HTML.
```sql Enrich a product master with live Amazon details, in one statement theme={"system"}
SELECT p.sku,
a.parsing:web_price::NUMBER(10, 2) AS amazon_price,
a.parsing:availability::BOOLEAN AS in_stock,
a.parsing:average_of_reviews::NUMBER(3, 2) AS rating
FROM products p,
TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'amazon_pdp',
OBJECT_CONSTRUCT('asin', p.amazon_asin)
)) a
WHERE p.amazon_asin IS NOT NULL
AND a.status = 'success';
```
One SQL statement, one Snowflake-native function — no stored procedures, no `CALL + RESULT_SCAN` round-trips, no Python middle-tier. dbt models, Snowflake Tasks, Streamlit apps, and any BI tool consume the output transparently.
## What you get
* **One UDTF, every [Extract Template](/nimble-sdk/agentic/agent-gallery).** Pass a template name (`amazon_pdp`, `google_maps_search`, `linkedin_search_companies`, `yelp_serp`, `walmart_serp`, `zillow_pdp`, …) and that template's input params. The same function shape covers every domain — retail, real estate, social, local business, healthcare, jobs. Despite its name, `NIMBLE_AGENT_RUN` runs Extract Templates, not Nimble Agents.
* **Typed structured output, no LLM in the loop.** Each template parses its target domain server-side and returns purpose-built fields — product title, price, ratings, review count, ranking position. No Cortex Complete pass needed to extract values from markdown.
* **One input row → one or many output rows.** PDP-style templates return a single product object per input. SERP-style templates return an array; pair the UDTF with `LATERAL FLATTEN` to expand into per-product rows.
* **Per-row error isolation.** A 429 on one input row surfaces as `status='http_429'` for that row only. The rest of the lateral join still completes — easy to filter, easy to retry.
* **Stays inside your Snowflake account.** Only authorized outbound calls hit Nimble. Secrets live in Snowflake's secret manager.
## Prerequisites
* `ACCOUNTADMIN` role for one-time setup
* Snowflake Enterprise edition or higher
* A Nimble API key ([sign up free](https://online.nimbleway.com/signup))
## Install in Snowflake
`NIMBLE_AGENT_RUN` shares the same role, database, warehouse, secret, and External Access Integration as `NIMBLE_SEARCH` and `NIMBLE_EXTRACT`. If you already deployed those for the [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) integration, skip ahead.
```sql theme={"system"}
USE ROLE ACCOUNTADMIN;
CREATE ROLE IF NOT EXISTS nimble_role;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE nimble_role;
CREATE DATABASE IF NOT EXISTS NIMBLE_INTEGRATION;
CREATE SCHEMA IF NOT EXISTS NIMBLE_INTEGRATION.TOOLS;
GRANT USAGE ON DATABASE NIMBLE_INTEGRATION TO ROLE nimble_role;
GRANT USAGE ON SCHEMA NIMBLE_INTEGRATION.TOOLS TO ROLE nimble_role;
CREATE WAREHOUSE IF NOT EXISTS NIMBLE_AGENT_WH
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE;
GRANT USAGE ON WAREHOUSE NIMBLE_AGENT_WH TO ROLE nimble_role;
CREATE OR REPLACE NETWORK RULE nimble_api_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('sdk.nimbleway.com:443');
CREATE OR REPLACE SECRET nimble_api_key
TYPE = GENERIC_STRING
SECRET_STRING = 'YOUR_NIMBLE_API_KEY';
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION nimble_eai
ALLOWED_NETWORK_RULES = (nimble_api_rule)
ALLOWED_AUTHENTICATION_SECRETS = (nimble_api_key)
ENABLED = TRUE;
GRANT USAGE ON INTEGRATION nimble_eai TO ROLE nimble_role;
```
Full setup script: role, grants, database, schema, warehouse, network rule, secret, EAI
A Python UDTF that wraps Nimble's [extract templates run API](https://docs.nimbleway.com/api-reference/extract-templates-api/run-extract-template). The signature is `(agent_name STRING, params OBJECT)` — every Extract Template's `params` body is a JSON object, so `OBJECT` removes the need for a `::VARIANT` cast on every call site.
```sql theme={"system"}
CREATE OR REPLACE FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
agent_name STRING,
params OBJECT
)
RETURNS TABLE (
task_id STRING,
status STRING,
status_code INTEGER,
url STRING,
parsing VARIANT,
warnings VARIANT,
raw VARIANT
)
LANGUAGE PYTHON
RUNTIME_VERSION = 3.11
PACKAGES = ('requests')
HANDLER = 'NimbleAgentRun'
EXTERNAL_ACCESS_INTEGRATIONS = (nimble_eai)
SECRETS = ('cred' = nimble_api_key)
AS $$
import _snowflake, requests
NIMBLE_AGENTS_RUN_URL = "https://sdk.nimbleway.com/v2/extract/templates/run"
class NimbleAgentRun:
def process(self, agent_name, params):
token = _snowflake.get_generic_secret_string("cred")
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"X-Client-Source": "snowflake-cortex-agent",
}
body = {"template": agent_name, "params": params or {}}
resp = requests.post(NIMBLE_AGENTS_RUN_URL, json=body,
headers=headers, timeout=120)
if resp.status_code >= 400:
err = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {"text": resp.text}
yield (None, f"http_{resp.status_code}", resp.status_code,
None, None, None, err)
return
data = resp.json()
agent_data = data.get("data") or {}
yield (
data.get("task_id"),
data.get("status"),
data.get("status_code"),
data.get("url"),
agent_data.get("parsing"),
data.get("warnings"),
data,
)
$$;
GRANT USAGE ON FUNCTION NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(STRING, OBJECT)
TO ROLE nimble_role;
```
Full UDTF with per-row error isolation, request-timeout handling, and smoke tests
Pick the response shape that matches the template you want to call. PDP-style templates (Amazon PDP, LinkedIn Profile, Walmart PDP, …) return a single product object. SERP-style templates (Amazon SERP, Google Search, …) return an array of products.
```sql theme={"system"}
-- PDP-style: parsing is a single object
SELECT parsing:product_title::STRING AS title,
parsing:web_price::NUMBER AS price,
parsing:availability::BOOLEAN AS in_stock
FROM TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'amazon_pdp',
OBJECT_CONSTRUCT('asin', 'B09B8V1LZ3')
));
-- SERP-style: parsing is an array; FLATTEN to one row per product
SELECT p.value:position::INTEGER AS position,
p.value:asin::STRING AS asin,
p.value:product_name::STRING AS title,
p.value:price::NUMBER(10, 2) AS price,
p.value:rating::NUMBER(3, 2) AS rating
FROM TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'amazon_serp',
OBJECT_CONSTRUCT('keyword', 'noise canceling headphones')
)) a,
LATERAL FLATTEN(INPUT => a.parsing) p
WHERE a.status = 'success'
ORDER BY position
LIMIT 10;
```
Both should return a populated result within \~30 seconds.
**Discover a template's field names before pinning a projection.** Each template in the [gallery](/nimble-sdk/agentic/agent-gallery) returns its own shape — `product_title` vs. `product_name`, `web_price` vs. `price`, `review_count` vs. `reviews_count`. Inspect once before wiring into a dbt model: `SELECT raw FROM TABLE(NIMBLE_AGENT_RUN(, …))` shows the full response; `SELECT OBJECT_KEYS(parsing[0]) FROM …` lists the per-product keys for SERP-style templates.
## Common workflows
### Local business discovery at scale
Build a fresh, location-aware view of every business that matters for a category in a market: feed a `LOCATION_QUERIES` table of research terms, get back the ranked Google Maps landscape — one row per business per query per refresh. Useful for market sizing, account-universe building, competitive territory mapping, and POI databases.
`google_maps_search` returns up to 20 results per query with rich per-place data: name, address, rating, review count, phone, business status, price level, sponsored flag, plus opening hours and accessibility metadata. The lateral join plus `LATERAL FLATTEN` on the nested results array is a single SQL statement.
```sql theme={"system"}
INSERT INTO LOCAL_BUSINESSES (query, category, position, name, address, city, rating, review_count, phone, business_status, price_level, sponsored, status, enriched_at)
SELECT
q.query,
q.category,
p.value:position::INTEGER AS position,
p.value:title::STRING AS name,
p.value:address::STRING AS address,
p.value:city::STRING AS city,
p.value:review_summary:overall_rating::NUMBER(3, 2) AS rating,
p.value:number_of_reviews::INTEGER AS review_count,
p.value:phone_number::STRING AS phone,
p.value:business_status::STRING AS business_status,
p.value:price_level::STRING AS price_level,
p.value:sponsored::BOOLEAN AS sponsored,
a.status AS status,
CURRENT_TIMESTAMP() AS enriched_at
FROM LOCATION_QUERIES q,
TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'google_maps_search',
OBJECT_CONSTRUCT('query', q.query)
)) a,
LATERAL FLATTEN(INPUT => a.parsing:entities:SearchResult) p
WHERE a.status = 'success';
```
**Input: `LOCATION_QUERIES`**
| query | category |
| - | - |
| coffee shop in austin texas | Food & Beverage |
| veterinary clinic in seattle wa | Pet care |
| boutique fitness studio in brooklyn ny | Fitness |
**Output: `LOCAL_BUSINESSES`** (one row per business per refresh)
| query | position | name | address | rating | review\_count | price\_level | sponsored |
| - | - | - | - | - | - | - | - |
| coffee shop in austin texas | 1 | Epoch Coffee | 221 W N Loop Blvd, … | 4.5 | 2481 | \$ | FALSE |
| coffee shop in austin texas | 2 | Mozart's Coffee | 3825 Lake Austin Blvd, … | 4.5 | 10618 | \$\$ | FALSE |
| coffee shop in austin texas | 3 | Jo's Coffee | 1300 S Congress Ave, … | 4.4 | 1958 | \$\$ | FALSE |
`google_maps_search` returns `parsing.entities.SearchResult` (a nested array inside the `parsing` dict), not a flat `parsing` array like Amazon SERP. `LATERAL FLATTEN(INPUT => a.parsing:entities:SearchResult)` is the correct unfold path. Different templates nest results differently — always inspect with `SELECT raw FROM …` once per template before pinning the FLATTEN target.
A `V_NEW_ENTRANTS` view can layer on top, flagging place IDs ranking in the top 20 for a tracked query for the first time in the last 7 days — useful for catching new market entrants before they take real share.
Companion cookbook recipe (Amazon SERP flavor): full notebook, sample data, the lateral-join INSERT, the V\_NEW\_ENTRANTS view, and the daily Task — drop-in adapt the template name and FLATTEN target to switch to `google_maps_search` or any other SERP-style Extract Template
### Product-master enrichment
Some templates return a single structured object instead of an array — Amazon PDP, LinkedIn Profile, Walmart PDP, Best Buy PDP. Use them when you already know which products you care about and want a full attribute dump per identifier: brand, description, color, packaging, pricing, availability, reviews.
```sql theme={"system"}
SELECT
p.sku,
a.parsing:brand::STRING AS brand,
a.parsing:product_title::STRING AS title,
a.parsing:brief_product_description::STRING AS description,
a.parsing:color::STRING AS color,
a.parsing:packaging::STRING AS packaging,
a.parsing:web_price::NUMBER(10, 2) AS price,
a.parsing:availability::BOOLEAN AS in_stock,
a.parsing:average_of_reviews::NUMBER(3, 2) AS rating
FROM PRODUCTS p,
TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'amazon_pdp',
OBJECT_CONSTRUCT('asin', p.amazon_asin)
)) a
WHERE p.amazon_asin IS NOT NULL
AND a.status = 'success';
```
One row per input row. No `LATERAL FLATTEN` needed — the template already returns the typed product as a single object. PDP-style templates typically return \~30 attribute fields per product; project whichever subset your downstream models need.
### Schedule recurring enrichment
`NIMBLE_AGENT_RUN` runs at a single SQL statement, which means a Snowflake Task can execute the enrichment directly. No wrapper procedure required.
```sql theme={"system"}
CREATE OR REPLACE TASK daily_local_business_discovery
WAREHOUSE = NIMBLE_AGENT_WH
SCHEDULE = 'USING CRON 0 8 * * * America/Los_Angeles'
AS
INSERT INTO LOCAL_BUSINESSES (query, category, position, name, address, city, rating, review_count, phone, business_status, price_level, sponsored, status, enriched_at)
SELECT
q.query,
q.category,
p.value:position::INTEGER AS position,
p.value:title::STRING AS name,
p.value:address::STRING AS address,
p.value:city::STRING AS city,
p.value:review_summary:overall_rating::NUMBER(3, 2) AS rating,
p.value:number_of_reviews::INTEGER AS review_count,
p.value:phone_number::STRING AS phone,
p.value:business_status::STRING AS business_status,
p.value:price_level::STRING AS price_level,
p.value:sponsored::BOOLEAN AS sponsored,
a.status AS status,
CURRENT_TIMESTAMP() AS enriched_at
FROM LOCATION_QUERIES q,
TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'google_maps_search',
OBJECT_CONSTRUCT('query', q.query)
)) a,
LATERAL FLATTEN(INPUT => a.parsing:entities:SearchResult) p
WHERE a.status = 'success';
ALTER TASK daily_local_business_discovery RESUME;
```
Reference cookbook Task (Amazon SERP flavor): incremental load, category column, error-row filtering — adapt the template name and FLATTEN target for `google_maps_search` or any other Extract Template
### Use as a dbt incremental model
The lateral-join + `FLATTEN` shape is also a valid dbt model body. Drop the SQL block above into `models/local_businesses.sql`, add a config block for incremental materialization, and dbt orchestrates the daily refresh — the same way it would for any other model.
```sql theme={"system"}
{{ config(
materialized='incremental',
unique_key=['query', 'position', 'enriched_at'],
on_schema_change='append_new_columns'
) }}
SELECT
q.query,
q.category,
p.value:position::INTEGER AS position,
p.value:title::STRING AS name,
p.value:address::STRING AS address,
p.value:review_summary:overall_rating::NUMBER(3, 2) AS rating,
p.value:number_of_reviews::INTEGER AS review_count,
a.status AS status,
CURRENT_TIMESTAMP() AS enriched_at
FROM {{ ref('location_queries') }} q,
TABLE(NIMBLE_INTEGRATION.TOOLS.NIMBLE_AGENT_RUN(
'google_maps_search',
OBJECT_CONSTRUCT('query', q.query)
)) a,
LATERAL FLATTEN(INPUT => a.parsing:entities:SearchResult) p
WHERE a.status = 'success'
{% if is_incremental() %}
AND CURRENT_TIMESTAMP() > (SELECT MAX(enriched_at) FROM {{ this }})
{% endif %}
```
## Roll out across your organization
* **Tune for your Nimble rate-limit tier.** Each `NIMBLE_AGENT_RUN` call is one upstream request — one per row in your input table. For high-cardinality inputs (thousands of rows), watch for 429s; they surface per-row as `status='http_429'`, so a single retry pass over those rows is the typical recovery. Nimble's [rate-limits page](/nimble-sdk/admin/rate-limits) lists per-tier ceilings.
* **Right-size the warehouse.** `XSMALL` with 60-second auto-suspend is fine for daily Task runs over a few hundred input rows. For larger enrichments (10K+ rows), step up to `SMALL` or `MEDIUM` only for the duration of the scheduled task; Snowflake bills per second.
* **Surface failures separately.** Filter `status='success'` for analytical views, but log non-success rows to a `_FAILURES` audit table — `status='http_429'`, `'http_4xx'`, `'request_error'` — so a sudden spike is visible without grepping Task logs.
* **Don't ship guesses.** Field names inside `parsing` differ per template. Inspect the shape once per template (`SELECT raw FROM …` or `SELECT OBJECT_KEYS(parsing[0]) FROM …`) before pinning a projection into a dbt model or production view.
## When to use which surface
The Snowflake integration ships three primitives across two pages. Pick by question:
| You want to… | Use | Page |
| - | - | - |
| Chat with an agent in Snowflake Intelligence that knows the web | A pre-built **Cortex Agent** wired to scalar UDFs | [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) |
| Call generic web search or page extraction inline in a `SELECT` | **`NIMBLE_SEARCH`** / **`NIMBLE_EXTRACT`** scalar UDFs | [Cortex Agents](/integrations/partnerships/snowflake/cortex-agents) |
| Enrich N warehouse rows with structured fields per row | **`NIMBLE_AGENT_RUN`** UDTF | This page |
Cortex Agent custom tools can only be scalar functions or procedures — they can't be UDTFs. So `NIMBLE_AGENT_RUN` lives here as a sibling surface, not as a third tool on the agent.
## Resources
Every SQL file referenced on this page, plus the Amazon keyword research recipe
Overview of templates — what they are, when to use them, how they parse
Browse the catalog of pre-built templates and their per-template parsing shapes
How Snowflake's tabular UDFs work — `process()` handler, `RETURNS TABLE`, lateral-join semantics
How Snowflake gates outbound HTTPS from UDFs and stored procedures
Scalar UDFs for Search + Extract, wired into a pre-built Cortex Agent
# Nimble Native App
Source: https://docs.nimbleway.com/integrations/partnerships/snowflake/native-app
Install Nimble's Snowflake Native Apps — Search and Agents (Extract Templates) — connect your API key inside Snowflake, and call live web data from SQL.
Nimble ships two Native Apps on the Snowflake Marketplace. Both run inside your account, call live web data from any worksheet, pipeline, or Snowflake Intelligence agent, and read your Nimble API key from a Snowflake Secret you control. They share the same one-time account setup, so connect your key once and use whichever app you install.
## Choose your app
Live web search and AI-synthesized answers. Procedures: `NIMBLE_SEARCH`, `NIMBLE_ANSWER`.
Site-specific extraction with Extract Templates (Amazon, Google, Walmart, Maps, and hundreds more). Procedures: `NIMBLE_AGENT_RUN`, `LIST_AGENTS`, `DESCRIBE_AGENT`.
Both apps install in one click from the Marketplace, ship a guided Streamlit setup screen, and keep your key inside your account — the app reads it from a Snowflake Secret at call time, and only authorized outbound calls reach Nimble.
## Prerequisites
* `ACCOUNTADMIN` role (or a role that can create applications, secrets, network rules, and external access integrations)
* A warehouse to run statements (any size; `XSMALL` is fine)
* A Nimble API key ([sign up free](https://online.nimbleway.com/signup), then copy it from [API keys](https://online.nimbleway.com/settings/api-keys))
## Set up account access
Both apps read your key from a Snowflake **Secret** and reach Nimble through an **External Access Integration**. These are account-level objects shared by either app, so create them once. The per-app sections below just bind to them.
Run this in a worksheet, in any database and schema you own (outside the app). Paste your Nimble API key in place of the placeholder.
```sql theme={"system"}
CREATE DATABASE IF NOT EXISTS nimble_integration;
CREATE SCHEMA IF NOT EXISTS nimble_integration.tools;
CREATE OR REPLACE SECRET nimble_integration.tools.nimble_api_key
TYPE = GENERIC_STRING
SECRET_STRING = 'YOUR_NIMBLE_API_KEY';
```
Create a network rule and an External Access Integration so an app can reach `sdk.nimbleway.com`.
```sql theme={"system"}
CREATE OR REPLACE NETWORK RULE nimble_integration.tools.nimble_api_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('sdk.nimbleway.com');
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION nimble_api_access
ALLOWED_NETWORK_RULES = (nimble_integration.tools.nimble_api_rule)
ALLOWED_AUTHENTICATION_SECRETS = (nimble_integration.tools.nimble_api_key)
ENABLED = TRUE;
```
## Nimble Search
Live web search and AI-synthesized answers as stored procedures: ranked web results as a `VARIANT`, or a single sourced answer string.
### Install from Marketplace
Open the [Nimble Search listing on the Snowflake Marketplace](https://app.snowflake.com/marketplace/listing/GZTSZ2BEO2R/nimble-nimble-search).
In Snowsight, go to **Data Products → Marketplace** and search for **Nimble Search**, or open the [listing](https://app.snowflake.com/marketplace/listing/GZTSZ2BEO2R/nimble-nimble-search) directly.
Click **Get**. Choose the database name the app installs into (the default is fine) and the role allowed to use it, then confirm. Snowflake installs the application into your account.
From **Data Products → Apps**, open **Nimble Search**. The setup screen lists three steps: connect your API key, approve external access, and create the procedures.
### Connect the app
The Secret and External Access Integration already exist from [Set up account access](#set-up-account-access). Bind both to the app, then create the procedures.
In the **Nimble Search** setup app:
* Click **Bind API key secret** and select `nimble_integration.tools.nimble_api_key`.
* Click **Approve external access** and select `nimble_api_access`.
Each opens a Snowflake dialog that grants the app scoped access to that one object.
Click **Create / refresh procedures**. This creates `CORE.NIMBLE_SEARCH` and `CORE.NIMBLE_ANSWER` with the references you just bound. When it returns `ok`, the app is ready.
Prefer SQL over the setup screen? See [Set up with SQL](#set-up-with-sql) below for the equivalent statements.
### Run it
Replace `nimble_search` with the database name you chose at install if it differs.
#### Get a direct answer
`NIMBLE_ANSWER` returns a single sourced answer string. It is the right shape for a Snowflake Intelligence agent tool.
```sql theme={"system"}
CALL nimble_search.core.nimble_answer('what is the capital of France?');
```
#### Search the web
`NIMBLE_SEARCH` returns a `VARIANT` with `results`, `request_id`, and `total_results`. The arguments are the query, the maximum number of results, and the search depth (`'lite'` or `'deep'`).
```sql theme={"system"}
CALL nimble_search.core.nimble_search('latest AI news', 5, 'lite');
```
#### Flatten results into rows
Run the search, then read its output with `RESULT_SCAN` and `FLATTEN` to get one row per result.
```sql theme={"system"}
CALL nimble_search.core.nimble_search('best espresso machine 2026', 5, 'lite');
SELECT v.value:title::STRING AS title,
v.value:url::STRING AS url,
v.value:description::STRING AS description,
v.value:metadata:position::INT AS position
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())),
LATERAL FLATTEN(input => $1:results) v
ORDER BY position;
```
#### Use the answer as a Cortex Agent tool
`CORE.NIMBLE_ANSWER(VARCHAR)` returns a single `STRING`, so it registers cleanly as a Cortex Agent tool. In Snowsight, go to **AI & ML → Agents → your agent → Tools → Add tool** and point it at `nimble_search.core.nimble_answer`. The agent can then pull live web context into its answers.
### Set up with SQL
The setup screen is the fastest path, but every step has a SQL equivalent. After [setting up account access](#set-up-account-access), bind the references and create the procedures directly.
```sql theme={"system"}
CALL nimble_search.config.register_reference(
'nimble_api_key', 'ADD',
SYSTEM$REFERENCE('SECRET','NIMBLE_INTEGRATION.TOOLS.NIMBLE_API_KEY','PERSISTENT','READ'));
CALL nimble_search.config.register_reference(
'nimble_external_access', 'ADD',
SYSTEM$REFERENCE('EXTERNAL_ACCESS_INTEGRATION','NIMBLE_API_ACCESS','PERSISTENT','USAGE'));
CALL nimble_search.core.create_eai_objects();
```
Both `register_reference` calls return `ok`, and `create_eai_objects` returns `ok` once the procedures exist.
## Nimble Agents
Run [Extract Templates](/nimble-sdk/web-tools/extract/template) — site-specific extraction for Amazon, Google, Walmart, Maps, social platforms, and hundreds of community templates — directly from SQL. The app also exposes template discovery in-warehouse, so you can find templates and inspect their inputs without leaving Snowflake. Despite the app and procedure names (`NIMBLE_AGENT_RUN`, `LIST_AGENTS`, `DESCRIBE_AGENT`), they operate on Extract Templates, not Nimble Agents.
### Install from Marketplace
Open the [Nimble Agents listing on the Snowflake Marketplace](https://app.snowflake.com/marketplace/listing/GZTSZ2BEO30/nimble-nimble-web-agents).
In Snowsight, go to **Data Products → Marketplace** and search for **Nimble Agents**, or open the [listing](https://app.snowflake.com/marketplace/listing/GZTSZ2BEO30/nimble-nimble-web-agents) directly. Click **Get**.
On the app's page, grant it `USAGE` on a warehouse when prompted (or from the setup app). The app needs compute to run its setup screen and procedures.
From **Data Products → Apps**, open **Nimble Agents**. The setup screen covers connecting your API key, approving external access, and creating the procedures.
### Connect the app
The Secret and External Access Integration already exist from [Set up account access](#set-up-account-access). Bind them to the app in order, then create the procedures.
In the **Nimble Agents** setup app:
* Click **Bind API key secret** and select `nimble_integration.tools.nimble_api_key`.
* Then click **Approve external access** (enabled only after the key is bound, so the integration is wired to your secret).
Click **Create / refresh procedures**. When it returns `ok`, the app is ready.
### Discover templates
The Extract Template catalog is large, so the app exposes discovery directly in SQL — no hunting through docs. Replace `nimble_agents` with the database name you chose at install if it differs.
```sql theme={"system"}
-- Search the catalog (keyword, domain, or vertical)
CALL nimble_agents.core.list_agents('amazon');
-- Flatten the matches into rows
SELECT a.value:name::STRING AS agent,
a.value:display_name::STRING AS title,
a.value:vertical::STRING AS vertical,
a.value:description::STRING AS description
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())),
LATERAL FLATTEN(input => $1) a;
-- Inspect one template's inputs before calling it
CALL nimble_agents.core.describe_agent('amazon_best_sellers');
-- returns input_properties (name, type, required, examples) + output_schema
```
The setup app's **Explore agents** section does the same visually, and links to the [Extract Template Gallery](https://online.nimbleway.com/gallery).
### Run a template
`NIMBLE_AGENT_RUN` returns a `VARIANT` with `status`, `task_id`, `url`, and a `data` payload. There are two forms — short (`agent`, `params`) and full (with optional `country`, `locale`).
```sql theme={"system"}
-- Short form
CALL nimble_agents.core.nimble_agent_run(
'amazon_best_sellers',
OBJECT_CONSTRUCT('category', 'appliances'));
-- Full form, with localization
CALL nimble_agents.core.nimble_agent_run(
'google_search',
OBJECT_CONSTRUCT('query', 'best espresso machine 2026'),
'us', 'en-US');
-- Read the parsed payload
SELECT $1:status::STRING AS status,
$1:data:parsing AS parsed
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
```
`agent` is the Extract Template identifier from `list_agents` (e.g. `google_search`, `amazon_pdp`). `params` is the template-specific input object — use `describe_agent` to see exactly what each template expects.
### Handle errors
Every procedure returns a `VARIANT`. On failure — an unknown template, a missing required parameter, or a transient upstream error — the procedure returns a structured error object instead of raising, so you can branch on it in SQL.
```sql theme={"system"}
CALL nimble_agents.core.describe_agent('does_not_exist');
SELECT $1:status::STRING AS status, -- 'error' when the call failed
$1:error:code::STRING AS code, -- e.g. not_found, invalid_input
$1:error:message::STRING AS message
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
```
Error `code` values: `invalid_input`, `not_found`, `unauthorized`, `rate_limited`, `invalid_request` / `upstream_error`, `timeout`, and `connection_error`.
### Use it from a Cortex Agent
`list_agents`, `describe_agent`, and `nimble_agent_run` register cleanly as Cortex Agent tools, so a Snowflake Intelligence chat can discover the right Extract Template and run it without the user knowing template names. Wire them in [AI & ML → Agents](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents), pointing each tool at the matching `core` procedure.
## Uninstall
Drop whichever app you installed. Replace the database names if you changed them at install.
```sql theme={"system"}
DROP APPLICATION nimble_search CASCADE;
DROP APPLICATION nimble_agents CASCADE;
```
The Secret, network rule, and External Access Integration you created are not owned by either app and remain in your account.
## Resources
Request shape, parameters, and response schema behind `NIMBLE_SEARCH`
Template identifiers, parameters, and execution models behind `NIMBLE_AGENT_RUN`
Browse every pre-built and community template
Wire Nimble tools into a Cortex Agent for Snowflake Intelligence
How Snowflake installs and runs apps inside a consumer account
How Snowflake gates outbound HTTPS from app procedures
# AdsPower
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/ads-power
Connect Nimble Proxy with AdsPower
AdsPower provides stealthy browsers that allow you to use multiple social or e-commerce accounts on a single computer simultaneously by providing a unique browser fingerprint for each account.
It also includes a browser automation tool that enables you to run multiple accounts simultaneously as if they were being accessed from different physical devices.
## **Follow these steps to integrate Nimble**
* Download and install the [AdsPower software here.](https://share.adspower.net/rf7gskzRbmRSqfn)
* Launch AdsPower and create a new browser profile.
* Click "New profile" to generate a new virtual browsing profile.
* Enter your profile name, then scroll down to the proxy configuration.
* Configure the proxy server with your browser profile. **HTTP is available with the Nimble IP.**
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the AdsPower platform.
Configure the proxy using the following information:
* Proxy type: HTTP
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
That's all! AdsPower will now use proxies from Nimble IP
# Android
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/android
Connect Nimble Proxy with Android
Android is by far the most popular mobile OS on the planet, with upwards of 70% market share.
Connecting an Android device to Nimble IP is easy and straightforward, with no 3rd-party apps needed!
## **Follow these steps to integrate Nimble**
* Fill in the Nimble IP proxy info in the setting proxy server window (don't forget to check "enable proxy server").
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the VMLogin platform.
1. To get started, open your device's settings, and then Internet/Network settings.
2. Next, click on the network you’re currently connected to in order to view its properties.
3. Click on the pencil at the top right to edit the network’s settings, and then on “Advanced options”.
4. Scroll down until you see “Proxy”. Set your proxy settings to manual. A few new fields will be added, including “Proxy hostname” and “Proxy port” :
* Hostname: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
5. At this point, your phone will begin sending your requests through Nimble IP, but you still need to authenticate your device. To do so, open Chrome and browse to any website. You’ll immediately be asked to sign in with a username and password :
* Username: your pipeline username
* Password: your pipeline password
6. Click Sign in, and you’re done!
1. Open your device's settings, and then click on Mobile Networks.
2. Next, click on “Access Point Names”, or on some devices APNs.
3. Click on the currently active APN.
4. Enter in the following details:
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
Because Android is used by many manufacturers, the process may be slightly different on your device. Furthermore, different manufacturers will allow different apps to use proxies, so be sure to check that the app you’d like to use supports proxies.
Congratulations! You’ve connected your Android device, and requests will now be routed through Nimble IP.
# FoxyProxy
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/foxy-proxy
Connect Nimble Proxy with FoxyProxy
FoxyProxy is a simple and free browser extension that lets you easily use Nimble IP on Firefox or Chrome.
While setting up your proxy server connection can be done without an extension, FoxyProxy provides a simple and flexible interface as well as additional options that browsers lack.
## **Follow these steps to integrate Nimble**
Installing FoxyProxy is very straightforward. \
Simply follow the steps on your browser of choice:
* [Chrome download page](https://chrome.google.com/webstore/detail/foxyproxy-standard/gcknhkkoolaabfmlnjonogaaifnjlfnp)
* [Firefox download page](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/)
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the FoxyProxy platform.
1. After downloading, click the extensions button on the upper right of Chrome and pin the extension to the toolbar
2. Next, click the FoxyProxy icon to open the quick activation menu, and select “Options” to modify the proxy settings.
3. On the left navigation menu, go to “Proxies”.
4. On the options menu to the right, select “Add New Proxy”.
5. In the Proxy Settings dialog that opens, enter the following credentials:
* Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
* Check the option to “Save Login Credentials” (click ‘OK’ on the message that pops up, explaining how FoxyProxy handles usernames and passwords)
6. Go to the “General” tab and set a name for your Proxy (we used Nimble)
7. There are two ways to enable the proxy:
1. From the FoxyProxy options page, change the proxy mode from “Disable FoxyProxy” to “Use proxy *yourProfile* for all URLs
2. Alternatively, you can use FoxyProxy’s quick menu to activate the proxy from any page. Click on the FoxyProxy icon in the toolbar, and then select your proxy profile
1. After downloading the add-on, Firefox will automatically add a shortcut to the toolbar. Simply click the FoxyProxy icon to open the quick activation menu, and then click “Options” to modify the proxy settings
2. On the left navigation menu, click “Add” to create a new proxy profile
3. In the “Add Proxy” page, set a name for this proxy setup (we used Nimble), then enter the following details:
* Proxy IP address or DNS name: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
4. There are two ways to enable the proxy:
1. From the FoxyProxy options page, change “Turn Off (Use Firefox Settings)” to the title you set for your proxy profile (we used “Nimble”)
2. Alternatively, you can use FoxyProxy’s quick menu to activate the proxy from any page. Click on the FoxyProxy icon in the toolbar, and then select your proxy profile
That's all! VMLogin will now use proxies from Nimble IP
# Incogniton
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/incogniton
Connect Nimble Proxy with Incogniton
Incogniton is a multi-session browser that helps you manage multiple online accounts from a single interface while maintaining your privacy and security.
Using Incogniton, you can create multiple virtual browser profiles that act as separate users, each with its own unique digital fingerprint. This allows you to access multiple accounts from a single device without compromising your data or leaving a trace.
## **Follow these steps to integrate Nimble**
* Sign up for a free account at [incogniton.com](http://incogniton.com).
* Download and install the Incogniton software [here](https://incogniton.com/?utm_source=proxy\&utm_campaign=nimbleway).
* Navigate to the "Profile Management" option on the left side of the screen.
* Click the "New profile" button in the top right corner.
* From the left navigation, select "Proxy" and choose the "Proxy type" from the options provided (HTTP, SOCK4, or SOCK5). **HTTP is available with the Nimble IP**.
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the Incogniton platform.
Configure the proxy using the following information:
* Proxy type: HTTP
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
That's all! Your Incogniton browser will now use proxies from Nimble IP.
# iOS
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/ios
Connect Nimble Proxy with iPhone and iPad
Setting up iOS to use Nimble IP is simple, straightforward, and requires no third-party apps. iOS natively supports proxy configuration for Wi-Fi connections.
By configuring Nimble on your iOS device, all network traffic on that Wi-Fi connection will route through Nimble's residential proxy network.
## **Follow these steps to integrate Nimble**
* Open the **Settings** app on your iPhone or iPad
* Tap **Wi-Fi**
* Tap the **info icon** (ⓘ) next to your connected Wi-Fi network
* Scroll down to the bottom of the network details
* Tap **Configure Proxy**
* Select **Manual**
Enter the following configuration:
* **Server:** ip.nimbleway.com
* **Port:** 7000
* **Authentication:** Toggle ON
* **Username:** your pipeline username
* **Password:** your pipeline password
In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
* Tap **Save** in the top-right corner
* Your device will now route traffic through Nimble's proxy network
The proxy configuration applies only to this specific Wi-Fi network. You'll need to repeat the setup for each Wi-Fi network where you want to use Nimble.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Server | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Authentication | ON | Enable credential authentication |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Verify your connection
To verify the proxy is working:
1. Open **Safari** or another browser
2. Visit [https://api.ipify.org](https://api.ipify.org) to check your IP address
3. The displayed IP should be different from your regular IP, confirming traffic is routed through Nimble
## Disabling the proxy
To disable the Nimble proxy:
1. Go to **Settings** → **Wi-Fi**
2. Tap the **info icon** (ⓘ) next to your Wi-Fi network
3. Tap **Configure Proxy**
4. Select **Off**
5. Tap **Save**
## Important notes
* **Per-network configuration:** Proxy settings are tied to specific Wi-Fi networks. If you connect to a different network, you'll need to configure the proxy again.
* **Cellular data:** This configuration only affects Wi-Fi traffic. Cellular data connections are not routed through the proxy.
* **All traffic:** Once enabled, all device traffic on that Wi-Fi network routes through Nimble, including apps and system services.
That's all! Your iOS device will now route Wi-Fi traffic through Nimble's proxy network.
# Kameleo
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/kameleo
Connect Nimble Proxy with Kameleo
Kameleo is a technology that allows you to browse the internet anonymously through stealth browsing. It has a range of tools, including a proprietary API and Selenium Stealth WebDriver, that can automate tasks and create multiple accounts while keeping your identity hidden.
The tool is supported by residential and mobile proxy networks, and this guide will show you how to use Nimble's proxy networks with Kameleo.
## **Follow these steps to integrate Nimble**
* Sign up for an account at [Kameleo.io](http://Kameleo.io)
* Download and install the Kameleo software [here](https://kameleo.io/?ref=13784).
* Navigate to the "New Profile" option on the left side of the screen.
* Choose the browser and OS footprint you wish to use.
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the Kameleo platform.
Configure the proxy using the following information:
* Proxy type: HTTP
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
That's all! Kameleo will now use proxies from Nimble IP
# macOS
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/macos
Connect Nimble Proxy with macOS System Preferences
macOS natively supports proxy servers like Nimble IP, allowing installed applications to use the proxy connection for their communications over the internet.
By configuring Nimble at the system level, all applications that respect system proxy settings will automatically route their traffic through Nimble's residential proxy network.
## **Follow these steps to integrate Nimble**
* Click the **Apple menu** () in the top-left corner
* Select **System Settings** (or **System Preferences** on older macOS versions)
* Click on **Network** in the sidebar
* Select your active network connection (e.g., **Wi-Fi** or **Ethernet**) from the list
* Click **Details...** (or **Advanced...** on older versions)
* Select the **Proxies** tab
* Enable **Secure Web Proxy (HTTPS)**
* Enter the proxy configuration:
* **Server:** ip.nimbleway.com
* **Port:** 7000
* Check **Proxy server requires password**
* Enter your credentials:
* **Username:** your pipeline username
* **Password:** your pipeline password
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
* Click **OK** to close the proxy settings
* Click **Apply** to save your network configuration
Your secure web traffic will now route through Nimble's proxy network.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Server | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Verify your connection
To verify the proxy is working:
1. Open **Safari** or another browser
2. Visit [https://api.ipify.org](https://api.ipify.org) to check your IP address
3. The displayed IP should be different from your regular IP, confirming traffic is routed through Nimble
## Disabling the proxy
To disable the Nimble proxy:
1. Return to **System Settings** → **Network** → **Details** → **Proxies**
2. Uncheck **Secure Web Proxy (HTTPS)**
3. Click **OK** and **Apply**
Some applications may have their own proxy settings that override system preferences. Check individual application settings if traffic isn't routing through the proxy as expected.
That's all! macOS will now route secure web traffic through Nimble's proxy network.
# MuLogin
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/mulogin
Connect Nimble Proxy with MuLogin
MuLogin is a multi-session browser that helps you manage multiple online accounts from a single interface while maintaining your privacy and security.
Using MuLogin, you can create multiple virtual browser profiles that act as separate users, each with its own unique digital fingerprint. This allows you to access multiple accounts from a single device without compromising your data or leaving a trace.
## **Follow these steps to integrate Nimble**
* Sign up for an account at [mulogin.com](https://www.mulogin.com/)
* Download and install the MuLogin software [here](https://www.mulogin.com/).
* Launch MuLogin and select "Add Browser" from the right-side menu.
* Locate the "Proxy Settings" section and choose "HTTP" as the protocol type.
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click "add pipeline" to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the MuLogin platform.
Configure the proxy using the following information:
* Proxy type: HTTP
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
Click "Check the network" to validate your proxy connection.
That's all! MuLogin will now use proxies from Nimble IP.
# Multilogin
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/multilogin
Connect Nimble Proxy with Multilogin
Multilogin provides you with browser and device fingerprints, and together with Nimble IP, you can access sites online from multiple destinations with full anonymity.
Using Multilogin's virtual browser profiles with Nimble's residential proxies, you can manage multiple online accounts while maintaining unique digital identities for each profile.
## **Follow these steps to integrate Nimble**
* Sign up for an account at [multilogin.com](https://multilogin.com/)
* Download and install the Multilogin software
* Launch Multilogin and log in to your account
* Click **Create New** to create a new browser profile
* Enter a name for your profile
* Select your preferred operating system and browser type
* Navigate to the **Proxy** settings section
* Click **Edit proxy settings**
* Select **HTTP** as the connection type
* Enter the following proxy details:
* **Address:** ip.nimbleway.com
* **Port:** 7000
* **Username:** your pipeline username
* **Password:** your pipeline password
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
Enable **"Timezone, WebRTC and Geolocation fingerprints based on the external IP"** for consistent fingerprinting that matches your proxy location.
* Click **Check proxy** to verify the connection is working
* You should see a success message with your proxy IP
* Click **Create profile** to save your configuration
Your Multilogin profile will now route all traffic through Nimble's proxy network.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Connection type | HTTP | Proxy protocol |
| Address | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Geo-targeting
You can control geo-targeting in two ways:
### Option 1: Pipeline settings (recommended)
Configure your target country directly in your Nimble pipeline settings. This applies to all connections using that pipeline.
### Option 2: Username string
Append the country code to your username for profile-level control:
```
your-username-country-us # Route through US
your-username-country-gb # Route through UK
your-username-country-de # Route through Germany
```
This is useful when you need different profiles to connect through different countries.
That's all! Multilogin will now use proxies from Nimble IP.
# Proxifier
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/proxifier
Connect Nimble Proxy with Proxifier
Proxifier is a network gateway that allows applications that do not support working through proxy servers to operate through a SOCKS or HTTPS proxy.
Using Proxifier with Nimble, you can selectively route specific applications through residential IPs without redirecting all your traffic through the proxy network.
## **Follow these steps to integrate Nimble**
* Download and install Proxifier from [proxifier.com](https://www.proxifier.com/)
* Launch Proxifier after installation
This guide uses Windows as an example, but macOS follows the same configuration steps.
* Navigate to **Profile** → **Proxy Servers**
* Click **Add** to create a new proxy server
* Enter the following server details:
* **Address:** ip.nimbleway.com
* **Port:** 7000
* **Protocol:** HTTPS
* Enable the **Authentication** checkbox
* Select **Username and Password**
* Enter your credentials:
* **Username:** your pipeline username
* **Password:** your pipeline password
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
* Click **Check** to test the connection
* You should see a success message confirming the proxy is working
* Click **OK** to save your configuration
We recommend you do not set Nimble IP as your default proxy server to avoid routing all traffic through the proxy unnecessarily.
## Adding proxy rules
You can configure which applications use the Nimble proxy:
1. Go to **Profile** → **Proxification Rules**
2. Click **Add** to create a new rule
3. Configure the rule:
* **Name:** Give your rule a descriptive name
* **Applications:** Select specific applications to route through the proxy
* **Target hosts:** Optionally specify domains or ports
* **Action:** Select your Nimble proxy server
This allows you to route only specific applications (like browsers or scraping tools) through Nimble while keeping other traffic direct.
## DNS configuration
For optimal performance, configure DNS resolution through Nimble:
1. Go to **Profile** → **Name Resolution**
2. Select **Resolve hostnames through proxy**
3. Click **OK** to save
This ensures DNS queries are also routed through Nimble's infrastructure.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Address | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Protocol | HTTPS | Secure proxy protocol |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
That's all! Proxifier will now route selected applications through Nimble's proxy network.
# Puppeteer
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/puppeteer
Connect Nimble Proxy with Puppeteer
Puppeteer is a Node.js library that provides a high-level API to control Chrome or Chromium browsers. It's commonly used for web scraping, automated testing, and generating screenshots or PDFs of web pages.
By integrating Nimble's proxy network with Puppeteer, you can route your automated browser requests through residential IPs, enabling access to geo-restricted content and avoiding IP-based blocking.
## Prerequisites
* Node.js installed on your system
* Nimble account with proxy credentials
## **Follow these steps to integrate Nimble**
Install Puppeteer and the proxy-chain package, which handles proxy authentication:
```bash theme={"system"}
npm install puppeteer proxy-chain
```
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click "add pipeline" to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Your proxy URL format will be: `http://username:password@ip.nimbleway.com:7000`
Use the following code to connect Puppeteer through Nimble's proxy:
```javascript theme={"system"}
const puppeteer = require('puppeteer');
const proxyChain = require('proxy-chain');
(async () => {
// Replace with your Nimble pipeline credentials
const proxyUrl = 'http://YOUR_USERNAME:YOUR_PASSWORD@ip.nimbleway.com:7000';
// Anonymize the proxy URL (handles authentication)
const anonymizedProxy = await proxyChain.anonymizeProxy(proxyUrl);
// Launch browser with proxy
const browser = await puppeteer.launch({
args: [
`--proxy-server=${anonymizedProxy}`,
'--no-sandbox',
'--disable-setuid-sandbox'
],
headless: true
});
try {
const page = await browser.newPage();
// Navigate to your target URL
await page.goto('https://example.com');
// Get page content
const content = await page.content();
console.log('Page loaded successfully');
// Take a screenshot (optional)
await page.screenshot({ path: 'screenshot.png' });
} finally {
// Clean up
await browser.close();
await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);
}
})();
```
The `proxy-chain` package is required because Puppeteer doesn't natively support proxy authentication. It creates a local proxy server that handles the authentication for you.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Host | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Geo-targeting example
To route requests through a specific country, append the country code to your username:
```javascript theme={"system"}
// Route through US proxies
const proxyUrl = 'http://YOUR_USERNAME-country-us:YOUR_PASSWORD@ip.nimbleway.com:7000';
// Route through UK proxies
const proxyUrl = 'http://YOUR_USERNAME-country-gb:YOUR_PASSWORD@ip.nimbleway.com:7000';
```
## Verify your connection
You can verify your proxy connection by checking your IP address:
```javascript theme={"system"}
const puppeteer = require('puppeteer');
const proxyChain = require('proxy-chain');
(async () => {
const proxyUrl = 'http://YOUR_USERNAME:YOUR_PASSWORD@ip.nimbleway.com:7000';
const anonymizedProxy = await proxyChain.anonymizeProxy(proxyUrl);
const browser = await puppeteer.launch({
args: [`--proxy-server=${anonymizedProxy}`],
headless: true
});
const page = await browser.newPage();
await page.goto('https://api.ipify.org?format=json');
const ipInfo = await page.evaluate(() => document.body.textContent);
console.log('Your proxy IP:', ipInfo);
await browser.close();
await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);
})();
```
That's all! Puppeteer will now route all requests through Nimble's proxy network.
# Scrapy
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/scrapy
Connect Nimble Proxy with Scrapy
Scrapy is a popular open-source web crawling and scraping framework for Python. It's used to crawl websites and extract structured data from their pages, making it ideal for data mining, monitoring, and automated testing.
By integrating Nimble with Scrapy, you can route your crawling requests through residential IPs, enabling access to geo-restricted content and avoiding IP-based blocking at scale.
## Prerequisites
* Python 3.8+ installed on your system
* Nimble account with API credentials
## **Follow these steps to integrate Nimble**
Install the official Nimble middleware for Scrapy:
```bash theme={"system"}
pip install scrapy-nimble
```
* Log in to your [Nimble Dashboard](https://app.nimbleway.com)
* Navigate to Settings → API Keys to find your API credentials
* You'll need your username and password for the configuration
Update your project's `settings.py` file to enable the Nimble middleware:
```python theme={"system"}
# Enable Nimble integration
NIMBLE_ENABLED = True
# Your Nimble credentials
NIMBLE_USERNAME = "your-username"
NIMBLE_PASSWORD = "your-password"
# Register the Nimble middleware
DOWNLOADER_MIDDLEWARES = {
"scrapy_nimble.middlewares.NimbleWebApiMiddleware": 570,
}
```
Ensure the Nimble middleware priority (570) is set to run before the default `HttpCompressionMiddleware` (590).
## Using Nimble in your spiders
Once configured, you can use Nimble features in your spider requests via the `meta` parameter:
```python theme={"system"}
import scrapy
class MySpider(scrapy.Spider):
name = "my_spider"
def start_requests(self):
yield scrapy.Request(
url="https://example.com",
callback=self.parse,
meta={
"nimble_render": True, # Enable JavaScript rendering
"nimble_country": "US", # Geo-target to US
"nimble_locale": "en", # Set locale
}
)
def parse(self, response):
# Your parsing logic here
title = response.css("title::text").get()
yield {"title": title}
```
## Available request options
| Meta Parameter | Type | Description |
| - | - | - |
| `nimble_render` | boolean | Enable JavaScript rendering |
| `nimble_country` | string | Two-letter country code for geo-targeting (e.g., "US", "DE", "GB") |
| `nimble_locale` | string | Locale setting for the request |
## Example spider with geo-targeting
```python theme={"system"}
import scrapy
class GeoSpider(scrapy.Spider):
name = "geo_spider"
countries = ["US", "GB", "DE", "FR"]
def start_requests(self):
for country in self.countries:
yield scrapy.Request(
url="https://example.com",
callback=self.parse,
meta={
"nimble_country": country,
"nimble_render": True,
},
cb_kwargs={"country": country}
)
def parse(self, response, country):
yield {
"country": country,
"title": response.css("title::text").get(),
"content_length": len(response.body),
}
```
## Development setup
For local development, we recommend using a virtual environment:
```bash theme={"system"}
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install scrapy scrapy-nimble
# Create a new Scrapy project
scrapy startproject myproject
```
That's all! Scrapy will now route all requests through Nimble's infrastructure.
# Selenium
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/selenium
Connect Nimble Proxy with Selenium
Selenium is a powerful browser automation framework used for web testing, scraping, and automating repetitive browser tasks. It supports multiple programming languages and browsers.
By integrating Nimble's proxy network with Selenium, you can route your automated browser requests through residential IPs, enabling access to geo-restricted content and avoiding IP-based blocking.
## Prerequisites
* Node.js installed on your system
* Nimble account with proxy credentials
## **Follow these steps to integrate Nimble**
Install Selenium WebDriver and the proxy-chain package:
```bash theme={"system"}
npm install selenium-webdriver proxy-chain
```
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click "add pipeline" to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Your proxy URL format will be: `http://username:password@ip.nimbleway.com:7000`
Use the following code to connect Selenium through Nimble's proxy:
```javascript theme={"system"}
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const proxyChain = require('proxy-chain');
(async () => {
// Replace with your Nimble pipeline credentials
const proxyUrl = 'http://YOUR_USERNAME:YOUR_PASSWORD@ip.nimbleway.com:7000';
// Anonymize the proxy URL (handles authentication)
const anonymizedProxy = await proxyChain.anonymizeProxy(proxyUrl);
// Configure Chrome options with proxy
const options = new chrome.Options();
options.addArguments(`--proxy-server=${anonymizedProxy}`);
options.addArguments('--no-sandbox');
options.addArguments('--disable-setuid-sandbox');
// Build the WebDriver
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
try {
// Navigate to your target URL
await driver.get('https://example.com');
// Get page title
const title = await driver.getTitle();
console.log('Page title:', title);
// Your automation logic here...
} finally {
// Clean up
await driver.quit();
await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);
}
})();
```
The `proxy-chain` package is required because Selenium doesn't natively support proxy authentication with Chrome. It creates a local proxy server that handles the authentication for you.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Host | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Geo-targeting example
To route requests through a specific country, append the country code to your username:
```javascript theme={"system"}
// Route through US proxies
const proxyUrl = 'http://YOUR_USERNAME-country-us:YOUR_PASSWORD@ip.nimbleway.com:7000';
// Route through Germany proxies
const proxyUrl = 'http://YOUR_USERNAME-country-de:YOUR_PASSWORD@ip.nimbleway.com:7000';
```
## Verify your connection
You can verify your proxy connection by checking your IP address:
```javascript theme={"system"}
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const proxyChain = require('proxy-chain');
(async () => {
const proxyUrl = 'http://YOUR_USERNAME:YOUR_PASSWORD@ip.nimbleway.com:7000';
const anonymizedProxy = await proxyChain.anonymizeProxy(proxyUrl);
const options = new chrome.Options();
options.addArguments(`--proxy-server=${anonymizedProxy}`);
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
await driver.get('https://api.ipify.org');
const ip = await driver.findElement({ css: 'body' }).getText();
console.log('Your proxy IP:', ip);
await driver.quit();
await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);
})();
```
That's all! Selenium will now route all requests through Nimble's proxy network.
# SwitchyOmega
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/switchyomega
Connect Nimble Proxy with SwitchyOmega browser extension
SwitchyOmega is a free browser extension that lets you manage and switch between multiple proxies quickly and easily. It supports both Chrome and Firefox with an intuitive interface.
Using SwitchyOmega with Nimble, you can easily toggle proxy connections on and off, and even set up automatic rules to route specific websites through different proxies.
## **Follow these steps to integrate Nimble**
Install the extension for your browser:
* **Chrome:** [Chrome Web Store](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif)
* **Firefox:** [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/switchyomega/)
After installation, pin the extension to your browser toolbar for easy access.
* Click the SwitchyOmega icon in your browser toolbar
* Select **Options** to open the settings interface
* In the left sidebar, click on **proxy** under Profiles
* Enter the following connection details:
* **Protocol:** HTTPS
* **Server:** ip.nimbleway.com
* **Port:** 7000
* Enter your credentials:
* **Username:** your pipeline username
* **Password:** your pipeline password
In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
* Click **Apply changes** to save your proxy settings
* Click the SwitchyOmega icon in your toolbar
* Select your **proxy** profile to activate the connection
Your browser traffic will now route through Nimble's proxy network.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Protocol | HTTPS | Secure proxy protocol |
| Server | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Auto-switch rules
SwitchyOmega allows you to automatically route specific websites through different proxies:
Create separate proxy profiles for different use cases (e.g., different geo-locations or pipelines):
* Click **New profile** in the left sidebar
* Select **Proxy Profile**
* Configure each profile with different Nimble pipeline credentials
* Click on **auto switch** in the left sidebar
* Add rules to route specific domains through specific profiles:
* **Condition type:** Host wildcard
* **Condition:** `*.example.com`
* **Profile:** Select which proxy profile to use
* Click **Apply changes**
* Click the SwitchyOmega icon in your toolbar
* Select **auto switch** to enable automatic profile switching
Now specific websites will automatically route through their assigned proxy profiles.
## Quick switching
Once configured, you can quickly switch between:
* **Direct** - No proxy (direct connection)
* **System Proxy** - Use system-level proxy settings
* **proxy** - Your Nimble proxy profile
* **auto switch** - Automatic rule-based switching
Just click the SwitchyOmega icon and select the desired mode.
That's all! SwitchyOmega will now manage your Nimble proxy connections.
# VMLogin
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/vm-login
Connect Nimble Proxy with VMLogin
With VMLogin's virtual browsing profiles, you can use physical devices to access and manage multiple online accounts while also benefiting from features like anti-association and fingerprint protection.
These virtual profiles help you protect your online identity and enhance the security of your online accounts.
## **Follow these steps to integrate Nimble**
* Sign up for an account at [VMLogin](https://www.vmlogin.us/?ref=nimble)
* Download and install the VMLogin software [here.](https://www.vmlogin.us/?ref=nimble)
* Launch VMLogin and create a new browser profile.
* Click "Get random profile" to generate a new virtual browsing profile.
* Customize your profile by selecting the settings that best suit you, including the operating system, screen resolution, language, WebGL vendor, time zone, and media device fingerprint.
* Fill in the Nimble IP proxy info in the setting proxy server window (don't forget to check "enable proxy server").
* In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page and click “add pipeline” to get your pipeline's proxy connection details.
* In your new pipeline, you will find the IP address, port, username, and password.
* Copy the proxy connection details from the Nimble user dashboard to the VMLogin platform.
Configure the proxy using the following information:
* Proxy type: HTTP
* IP address/Host: [ip.nimbleway.com](http://ip.nimbleway.com)
* Port: 7000
* Username: your pipeline username
* Password: your pipeline password
That's all! VMLogin will now use proxies from Nimble IP
# Windows
Source: https://docs.nimbleway.com/integrations/proxy/integration-guides/windows
Connect Nimble Proxy with Windows System Settings
Windows 10 and later natively support proxy servers like Nimble IP. The operating system manages server and port settings, while each application handles its own authentication credentials.
By configuring Nimble at the system level, applications that respect system proxy settings will route their traffic through Nimble's residential proxy network.
## **Follow these steps to integrate Nimble**
* Press the **Windows key** and search for "proxy settings"
* Or navigate to **Settings** → **Network & Internet** → **Proxy**
* Scroll down to **Manual proxy setup**
* Click **Set up** (or toggle **Use a proxy server** to ON on older Windows versions)
* Enter the proxy configuration:
* **Proxy IP address:** ip.nimbleway.com
* **Port:** 7000
* Click **Save**
Windows 10+ requires applications to manage proxy authentication individually. When you access a website through your browser, you'll receive a prompt requesting your Nimble credentials:
* **Username:** your pipeline username
* **Password:** your pipeline password
In the Nimble User Dashboard, navigate to the [Pipelines](https://app.nimbleway.com/pipelines) page to get your credentials.
Most browsers will remember your credentials after the first entry, so you won't need to re-enter them for each request.
## Configuration options
| Parameter | Value | Description |
| - | - | - |
| Proxy IP address | `ip.nimbleway.com` | Nimble proxy endpoint |
| Port | `7000` | Default proxy port |
| Username | Your pipeline username | From your Nimble dashboard |
| Password | Your pipeline password | From your Nimble dashboard |
## Alternative: IP allowlisting
Instead of using username/password authentication, you can allowlist your IP address:
1. Log in to your [Nimble Dashboard](https://app.nimbleway.com)
2. Navigate to your Pipeline settings
3. Add your public IP address to the allowlist
4. Connect to the proxy without credentials
This is useful for automated systems or when you prefer not to enter credentials.
## Geo-targeting
To route requests through a specific country, modify your username:
```
your-username-country-us # Route through US
your-username-country-gb # Route through UK
your-username-country-de # Route through Germany
```
## Disabling the proxy
To disable the Nimble proxy:
1. Return to **Settings** → **Network & Internet** → **Proxy**
2. Toggle **Use a proxy server** to OFF
3. Or click **Set up** and disable the proxy
That's all! Windows will now route web traffic through Nimble's proxy network.
# Overview
Source: https://docs.nimbleway.com/integrations/proxy/overview
Step-by-step guides for integrating Nimble IP with popular third-party software
Nimble IP is optimized for compatibility with third-party apps. To help you get started, we've prepared step-by-step guides for integrating Nimble IP with popular third-party software.
***
## Anti-Detect Browsers
Manage multiple accounts with unique browser fingerprints while routing traffic through Nimble's residential proxies.
Virtual browser profiles with fingerprint protection
Multi-session browser for account management
Browser fingerprints with full anonymity
Stealth browsers for multiple accounts
Anonymous stealth browsing technology
Multi-session browser with unique fingerprints
***
## Browser Extensions and Desktop Apps
Quickly switch between proxies directly from your browser or Desktop.
Firefox and Chrome proxy management extension
Manage and switch between multiple proxies
Network gateway for any application
***
## Automation Tools
Integrate Nimble proxies into your web scraping and automation workflows.
Node.js browser automation library
Browser automation framework
Python web crawling framework
***
## Operating Systems
Configure system-level proxy settings for your device.
Windows 10+ system proxy configuration
macOS system preferences proxy setup
iPhone and iPad Wi-Fi proxy configuration
Android device proxy setup
***
## Quick Reference
All integrations use the same core proxy settings:
| Parameter | Value |
| - | - |
| **Host** | `ip.nimbleway.com` |
| **Port** | `7000` |
| **Protocol** | HTTP / HTTPS |
| **Username** | Your pipeline username |
| **Password** | Your pipeline password |
Get your proxy credentials from the [Pipelines](https://app.nimbleway.com/pipelines) page in your Nimble dashboard.
# Account Management
Source: https://docs.nimbleway.com/nimble-sdk/admin/account-management
Manage API keys and collaborate with your team
Manage your Nimble account settings, generate API keys, and collaborate with team members all from one centralized dashboard.
## Accessing Settings
Navigate to your settings at [online.nimbleway.com](https://online.nimbleway.com):
1. Log in with your username and password
2. Find the **Settings** section in the left sidebar
3. Select **API Keys** or **Billing**
***
## API Keys
API keys are used to authenticate your requests to the Nimble API. Each key is unique and should be kept secure.
### Generate Your First API Key
Under **Settings** in the left sidebar, select **API Keys**
Click **Create key**
Give your API key a descriptive name
**Important**: Your API key will only be displayed once. Make sure to copy it immediately and store it securely.
Click **Copy** to save your API key to clipboard
### Managing API Keys
Once created, you can view and manage all your API keys from the API Keys dashboard.
#### API Key Information
Each API key displays the following information:
| Column | Description |
| - | - |
| **Name** | The descriptive name you gave to the key |
| **Created** | Date and time when the key was created |
| **Created By** | The team member who created the key |
| **Last Used** | Most recent date the key was used in an API request |
| **Actions** | Delete or manage the key |
### Delete an API Key
Deleting an API key is permanent and cannot be undone. Any applications using this key will immediately lose access.
To delete an API key:
1. Locate the key you want to delete in the list
2. Click the **Delete** button or trash icon
3. Confirm the deletion in the popup dialog
### Best Practices
Name keys based on their purpose or environment
Create new keys periodically and delete old ones to maintain security
Use separate API keys for different services to track usage and limit exposure
Keep API keys confidential and never commit them to version control
Monitor the "Last Used" column to identify and remove unused API keys
***
## Team Members
Collaborate with your team by inviting members to your Nimble account. Control access levels and manage permissions all in one place.
### Invite Team Members
In the dashboard, open **Team Members**
Click the **Invite Team Member** button
Type the email address of the person you want to invite
Click **Send Invite** - they'll receive an email with instructions to join
### Managing Team Members
View and manage all team members from the Team Members dashboard.
#### Team Member Information
| Column | Description |
| - | - |
| **Name** | Team member's full name |
| **Email** | Associated email address |
| **Role** | Permission level (Member or Admin) |
| **Status** | Active, Pending, or Invited |
| **Joined** | Date when they accepted the invitation |
| **Actions** | Change role or remove member |
### Change Member Role
Promote members to admins or revoke admin privileges:
1. Locate the team member in the list
2. Click the **Role** dropdown
3. Select **Admin** or **Member**
4. Changes take effect immediately
Admins have full access to account settings, billing, API keys, and team management
### Remove Team Members
To remove a team member from your account:
1. Find the member in the team list
2. Click the **Remove** button or trash icon
3. Confirm the removal
Removed members will immediately lose access to your account and all its resources
### Permission Levels
**Members can:**
* Use existing API keys
* View usage statistics
* Access documentation
* Make API requests
**Members cannot:**
* Create or delete API keys
* Invite or remove team members
* Access billing information
* Change account settings
**Admins can:**
* Everything Members can do
* Create and delete API keys
* Invite and remove team members
* Promote/demote member roles
* Access billing and payment information
* Modify account settings
**Full account control with no restrictions**
### Team Collaboration Tips
Create unique API keys for each team member to track individual usage and maintain accountability
Only promote trusted team members to admin roles to maintain account security
Encourage team members to use clear names when creating API keys
Periodically review team members and API keys to remove unused accounts
***
## Security Best Practices
* Never commit API keys to version control
* Use environment variables to store keys
* Rotate keys regularly (every 90 days recommended)
* Delete keys immediately if compromised
* Use separate keys for development and production
* Grant minimum necessary permissions
* Remove access for departing team members immediately
* Use admin roles sparingly
* Review team member list regularly
* Monitor API key usage patterns
* Check "Last Used" dates on API keys
* Review team member activity
* Set up alerts for unusual usage
* Track API consumption by key
* Audit changes to account settings
***
## Need Help?
Learn how to use your API keys with our quickstart guide
Contact our support team for account assistance
Test & Explore the API effortleslly, no code is needed
# Blogs
Source: https://docs.nimbleway.com/nimble-sdk/admin/blogs
# Callbacks & Delivery
Source: https://docs.nimbleway.com/nimble-sdk/admin/callbacks-and-delivery
Configure how you receive results from async operations
When using async operations like `/extract/async`, `/extract/batch`, `/extract/templates/async`, or `/crawl`, you have three flexible options for receiving your results. Choose the method that best fits your infrastructure and workflow.
Pull results on-demand using task IDs
Receive push notifications when tasks complete
Automatic delivery to your S3 or GCS bucket
***
## Option 1: Polling (Pull)
The simplest approach - submit your async request, receive a task ID, and poll for results when ready.
Send a request to the async endpoint. You'll receive a task or crawl ID to track your request.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.async_(
url="https://www.nimbleway.com",
render=True,
formats=["html", "markdown"]
)
task_id = response.task.id
print(f"Task submitted: {task_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.async({
url: "https://www.nimbleway.com",
render: true,
formats: ["html", "markdown"]
});
const taskId = response.task.id;
console.log(`Task submitted: ${taskId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"render": true,
"formats": ["html", "markdown"]
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.templates.async_(
template="amazon_pdp",
params={"asin": "B0DLKFK6LR"}
)
task_id = response.task.id
print(f"Task submitted: {task_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.templates.async({
template: "amazon_pdp",
params: { asin: "B0DLKFK6LR" }
});
const taskId = response.task.id;
console.log(`Task submitted: ${taskId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/templates/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"template": "amazon_pdp",
"params": { "asin": "B0DLKFK6LR" }
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
limit=50
)
crawl_id = result.crawl_id
print(f"Crawl started: {crawl_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
limit: 50
});
const crawlId = result.crawl_id;
console.log(`Crawl started: ${crawlId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"limit": 50
}'
```
Submit up to 1,000 URLs in one request. Each URL becomes its own task, tracked under a shared `batch_id`.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.batch(
inputs=[
{"url": "https://www.example.com/page1"},
{"url": "https://www.example.com/page2"},
{"url": "https://www.example.com/page3"},
],
shared_inputs={
"render": True,
"country": "US",
"formats": ["markdown"],
}
)
batch_id = response.batch_id
print(f"Batch submitted: {batch_id} ({response.batch_size} tasks)")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.batch({
inputs: [
{ url: "https://www.example.com/page1" },
{ url: "https://www.example.com/page2" },
{ url: "https://www.example.com/page3" },
],
shared_inputs: {
render: true,
country: "US",
formats: ["markdown"],
},
});
const batchId = response.batch_id;
console.log(`Batch submitted: ${batchId} (${response.batch_size} tasks)`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/batch' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": [
{"url": "https://www.example.com/page1"},
{"url": "https://www.example.com/page2"},
{"url": "https://www.example.com/page3"}
],
"shared_inputs": {
"render": true,
"country": "US",
"formats": ["markdown"]
}
}'
```
Poll the status endpoint to monitor progress.
```python Python theme={"system"}
import time
while True:
my_task = nimble.tasks.get(task_id)
print(f"Status: {my_task.task.state}")
if my_task.task.state == "success":
break
elif my_task.task.state == "error":
print(f"Task failed: {my_task.task.error}")
break
time.sleep(15)
```
```typescript TypeScript theme={"system"}
while (true) {
const myTask = await nimble.tasks.get(taskId);
console.log(`Status: ${myTask.task.state}`);
if (myTask.task.state === "success") break;
if (myTask.task.state === "error") {
console.log(`Task failed: ${myTask.task.error}`);
break;
}
await new Promise(resolve => setTimeout(resolve, 15000));
}
```
```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}' \
--header 'Authorization: Bearer YOUR-API-KEY'
# Response: { "task": { "id": "...", "state": "success", ... } }
```
Use the lightweight progress endpoint for polling. Once `completed` is `true`, fetch task details.
```python Python theme={"system"}
import time
while True:
progress = nimble.batches.progress(batch_id)
print(f"Progress: {progress.completed_count} / {progress.total} ({progress.progress:.0%})")
if progress.completed:
break
time.sleep(15)
# Get all task IDs and states
batch = nimble.batches.get(batch_id)
for task in batch.tasks:
print(f"Task {task.id}: {task.state}")
```
```typescript TypeScript theme={"system"}
while (true) {
const progress = await nimble.batches.progress(batchId);
console.log(`Progress: ${progress.completed_count} / ${progress.total} (${Math.round(progress.progress * 100)}%)`);
if (progress.completed) break;
await new Promise(resolve => setTimeout(resolve, 15000));
}
// Get all task IDs and states
const batch = await nimble.batches.get(batchId);
batch.tasks.forEach(task => {
console.log(`Task ${task.id}: ${task.state}`);
});
```
```bash cURL theme={"system"}
# Lightweight progress check
curl 'https://sdk.nimbleway.com/v2/batches/{batch_id}/progress' \
--header 'Authorization: Bearer YOUR-API-KEY'
# Full batch details (when complete)
curl 'https://sdk.nimbleway.com/v2/batches/{batch_id}' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
Crawl has its own status endpoint that shows overall progress and individual page tasks.
```python Python theme={"system"}
import time
while True:
my_crawl = nimble.crawl.status(crawl_id)
print(f"Status: {my_crawl.status}")
if status.state == "succeeded":
break
elif status.state == "failed":
print(f"Task failed: {status.error}")
break
time.sleep(2)
```
```typescript TypeScript theme={"system"}
while (true) {
const myCrawl = await nimble.crawl.status(crawlId);
console.log(`Status: ${myCrawl.status}`);
if (status.state === "succeeded") break;
if (status.state === "failed") {
console.log(`Task failed: ${status.error}`);
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
```
```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/crawl/{crawl_id}' \
--header 'Authorization: Bearer YOUR-API-KEY'
# Response: { "crawl": { "status": "running", "completed": 10, "total": 50, "tasks": [...] } }
```
Once complete, fetch the full results.
```python Python theme={"system"}
results = nimble.tasks.results(task_id)
print(f"HTML length: {len(results.data.html)}")
print(f"Markdown length: {len(results.data.markdown)}")
```
```typescript TypeScript theme={"system"}
const results = await nimble.tasks.results(taskId);
console.log(`HTML length: ${results.data.html.length}`);
console.log(`Markdown length: ${results.data.markdown.length}`);
```
```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
```python Python theme={"system"}
results = nimble.tasks.results(task_id)
parsed = results.data.parsing
print(f"Product: {parsed['product_title']}")
print(f"Price: ${parsed['web_price']}")
```
```typescript TypeScript theme={"system"}
const results = await nimble.tasks.results(taskId);
const parsed = results.data.parsing;
console.log(`Product: ${parsed.product_title}`);
console.log(`Price: $${parsed.web_price}`);
```
```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
Fetch results for each completed task in the crawl.
```python Python theme={"system"}
# my_crawl["tasks"] from step #2 contains list of task IDs from status response
for task in my_crawl["tasks"]:
if task.state == "success":
task_result = nimble.tasks.results(task.id)
print(f"URL: {task_result.url}")
print(f"HTML length: {len(task_result.data.get('html', ''))}")
```
```typescript TypeScript theme={"system"}
// batch.tasks from step #2 contains task objects with id and state
for (const task of myCrawl.tasks) {
if (task.state === "success") {
const results = await nimble.tasks.results(task.id);
console.log(`URL: ${results.url}`);
console.log(`HTML length: ${results.data?.html?.length || 0}`);
}
}
```
```bash cURL theme={"system"}
# Get results for each task_id from the crawl status response
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
Once the batch is complete, retrieve results for each successful task using the task IDs from `GET /v2/batches/{batch_id}`.
```python Python theme={"system"}
# batch.tasks from step #2 — iterate and fetch each result
for task in batch.tasks:
if task.state == "success":
result = nimble.tasks.results(task.id)
print(f"Markdown: {result.data.markdown[:200]}")
elif task.state == "error":
print(f"Task {task.id} failed: {task.error}")
```
```typescript TypeScript theme={"system"}
// batch.tasks from step #2 — iterate and fetch each result
for (const task of batch.tasks) {
if (task.state === "success") {
const result = await nimble.tasks.results(task.id);
console.log(`Markdown: ${result.data.markdown.substring(0, 200)}`);
} else if (task.state === "error") {
console.log(`Task ${task.id} failed: ${task.error}`);
}
}
```
```bash cURL theme={"system"}
# Fetch results for each task_id from the batch details response
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
```json theme={"system"}
{
"url": "https://www.nimbleway.com/blog/post",
"task_id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"status": "success",
"task": {
"id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"state": "success",
"created_at": "2026-02-09T23:15:43.549Z",
"modified_at": "2026-02-09T23:16:39.094Z",
"account_name": "your-account"
},
"data": {
"html": "...",
"markdown": "# Page Title\n\nContent...",
"headers": { ... }
},
"metadata": {
"query_time": "2026-02-09T23:15:43.549Z",
"query_duration": 1877,
"response_parameters": {
"input_url": "https://www.nimbleway.com/blog/post"
},
"driver": "vx6"
},
"status_code": 200
}
```
### Polling endpoints reference
| API | Submit | Check Status | Get Results |
| - | - | - | - |
| Extract | `POST /v2/extract/async` | `GET /v2/tasks/{task_id}` | `GET /v2/tasks/{task_id}/results` |
| Extract Template | `POST /v2/extract/templates/async` | `GET /v2/tasks/{task_id}` | `GET /v2/tasks/{task_id}/results` |
| Batch | `POST /v2/extract/batch` | `GET /v2/batches/{batch_id}/progress` | `GET /v2/tasks/{task_id}/results` (per task) |
| Crawl | `POST /v2/crawl` | `GET /v2/crawl/{crawl_id}` | `GET /v2/tasks/{task_id}/results` (per page) |
To list all tasks across your account, use `GET /v2/tasks` (supports `cursor` and `limit` for pagination). To list all batches, use `GET /v2/batches`.
***
## Option 2: Webhooks (Push)
Get notified automatically when your tasks complete. Perfect for event-driven architectures.
Include `callback_url` (or `callback` object for crawl) in your async request.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.async_(
url="https://www.nimbleway.com",
render=True,
formats=["html", "markdown"],
callback_url="https://your-server.com/webhooks/nimble"
)
task_id = response.task.id
print(f"Task submitted: {task_id}")
print("Results will be POSTed to your callback URL when ready")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.async({
url: "https://www.nimbleway.com",
render: true,
formats: ["html", "markdown"],
callback_url: "https://your-server.com/webhooks/nimble"
});
const taskId = response.task.id;
console.log(`Task submitted: ${taskId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"render": true,
"formats": ["html", "markdown"],
"callback_url": "https://your-server.com/webhooks/nimble"
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.async_(
url="https://www.nimbleway.com",
render=True,
formats=["html", "markdown"],
callback_url="https://your-server.com/webhooks/nimble"
)
task_id = response.task.id
print(f"Task submitted: {task_id}")
print("Results will be POSTed to your callback URL when ready")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.templates.async({
template: "amazon_pdp",
params: { asin: "B0DLKFK6LR" },
callback_url: "https://your-server.com/webhooks/nimble",
});
const taskId = response.task.id;
console.log(`Task submitted: ${taskId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"render": true,
"formats": ["html", "markdown"],
"callback_url": "https://your-server.com/webhooks/nimble"
}'
```
Crawl uses a `callback` object for advanced webhook options.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
limit=100,
callback={
"url": "https://your-server.com/webhooks/nimble",
"headers": {
"X-Custom-Auth": "your-secret-token"
},
"events": ["completed", "failed"]
}
)
print(f"Crawl started: {result.crawl_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
limit: 100,
callback: {
url: "https://your-server.com/webhooks/nimble",
headers: {
"X-Custom-Auth": "your-secret-token"
},
events: ["completed", "failed"]
}
});
console.log(`Crawl started: ${result.crawl_id}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"limit": 100,
"callback": {
"url": "https://your-server.com/webhooks/nimble",
"headers": { "X-Custom-Auth": "your-secret-token" },
"events": ["completed", "failed"]
}
}'
```
Nimble sends a POST to your callback URL when complete:
```json theme={"system"}
{
"task": {
"id": "8e8cfde8-345b-42b8-b3e2-0c61eb11e00f",
"state": "success",
"status_code": 200,
"created_at": "2026-01-24T12:36:24.685Z",
"modified_at": "2026-01-24T12:36:24.685Z",
"input": {},
"api_type": "extract"
}
}
```
### Webhook configuration options
| API | Parameter | Type | Description |
| - | - | - | - |
| Extract | `callback_url` | string | Your callback URL |
| Extract Templates | `callback_url` | string | Your callback URL |
| Crawl | `callback.url` | string | Your callback URL |
| | `callback.headers` | object | Custom headers for authentication |
| | `callback.metadata` | object | Custom data included in payload |
| | `callback.events` | array | Filter events: `started`, `page`, `completed`, `failed` |
***
## Option 3: Cloud Delivery (Async API)
Applies to async API requests - `/extract/async`, `/extract/batch`, `/extract/templates/async`, `/crawl`. For connecting a **Job** to your storage, see [Job Connections](/nimble-sdk/agentic/jobs-connections).
Automatically deliver results directly to your cloud storage bucket.
Deliver to any S3 bucket in your AWS account
Deliver to any GCS bucket in your GCP project
Grant Nimble's service account write access to your bucket.
**Nimble Service User ARN:**
```
arn:aws:iam::744254827463:user/webit-uploader
```
Add this bucket policy:
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "NimbleCloudDelivery",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::744254827463:user/webit-uploader"
},
"Action": [
"s3:PutObject",
"s3:PutObjectACL",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME",
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
}
]
}
```
Replace `YOUR_BUCKET_NAME` with your actual bucket name.
For KMS-encrypted buckets, add this to your KMS key policy:
```json theme={"system"}
{
"Sid": "NimbleKMSAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::744254827463:user/webit-uploader"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
```
**Nimble Service Account:**
```
nimbleway-gcp-storage@nimbleway-gcp.iam.gserviceaccount.com
```
1. Navigate to your bucket in the [Google Cloud Console](https://console.cloud.google.com/storage/browser)
2. Click on the **Permissions** tab
3. Click **Grant Access**
4. Add principal: `nimbleway-gcp-storage@nimbleway-gcp.iam.gserviceaccount.com`
5. Assign role: **Storage Object Creator**
6. Click **Save**
Include `storage_type` and `storage_url` in your request.
#### Cloud delivery parameters
| Parameter | Type | Description |
| - | - | - |
| `storage_type` | `s3` \| `gs` | Cloud provider |
| `storage_url` | string | Bucket path with prefix (e.g., `s3://bucket/prefix/`) |
| `storage_compress` | boolean | Enable GZIP compression |
| `storage_object_name` | string | Custom filename (default: task ID) |
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.async_(
url="https://www.nimbleway.com",
render=True,
formats=["html", "markdown"],
storage_type="s3",
storage_url="s3://your-bucket/nimble-results/",
storage_compress=True,
storage_object_name="my-result"
)
task_id = response.task.id
print(f"Results will be saved to: s3://your-bucket/nimble-results/my-result.json.gz")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.async({
url: "https://www.nimbleway.com",
render: true,
formats: ["html", "markdown"],
storage_type: "s3",
storage_url: "s3://your-bucket/nimble-results/",
storage_compress: true,
storage_object_name: "my-result"
});
const taskId = response.task.id;
console.log(`Results will be saved to: s3://your-bucket/nimble-results/my-result.json.gz`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"render": true,
"formats": ["html", "markdown"],
"storage_type": "s3",
"storage_url": "s3://your-bucket/nimble-results/",
"storage_compress": true,
"storage_object_name": "my-result"
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.async_(
url="https://www.nimbleway.com",
render=True,
formats=["html", "markdown"],
storage_type="gs",
storage_url="gs://your-bucket/nimble-results/",
storage_object_name="my-result"
)
task_id = response.task.id
print(f"Results will be saved to: gs://your-bucket/nimble-results/my-result.json")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.async({
url: "https://www.nimbleway.com",
render: true,
formats: ["html", "markdown"],
storage_type: "gs",
storage_url: "gs://your-bucket/nimble-results/",
storage_object_name: "my-result"
});
const taskId = response.task.id;
console.log(`Results will be saved to: gs://your-bucket/nimble-results/my-result.json`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"render": true,
"formats": ["html", "markdown"],
"storage_type": "gs",
"storage_url": "gs://your-bucket/nimble-results/",
"storage_object_name": "my-result"
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.templates.async_(
template="amazon_pdp",
params={"asin": "B0DLKFK6LR"},
storage_type="s3",
storage_url="s3://your-bucket/nimble-results/",
storage_compress=True,
storage_object_name="my-result"
)
task_id = response.task.id
print(f"Results will be saved to: s3://your-bucket/nimble-results/my-result.json.gz")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.templates.async({
template: "amazon_pdp",
params: { asin: "B0DLKFK6LR" },
storage_type: "s3",
storage_url: "s3://your-bucket/nimble-results/",
storage_compress: true,
storage_object_name: "my-result"
});
const taskId = response.task.id;
console.log(`Results will be saved to: s3://your-bucket/nimble-results/my-result.json.gz`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/templates/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"template": "amazon_pdp",
"params": {
"asin": "B0DLKFK6LR",
"storage_type": "s3",
"storage_url": "s3://your-bucket/nimble-results/",
"storage_compress": true,
"storage_object_name": "my-result"
}
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.extract.templates.async_(
template="amazon_pdp",
params={"asin": "B0DLKFK6LR"},
storage_type="gs",
storage_url="gs://your-bucket/nimble-results/",
storage_object_name="my-result"
)
task_id = response.task.id
print(f"Results will be saved to: gs://your-bucket/nimble-results/my-result.json")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.extract.templates.async({
template: "amazon_pdp",
params: { asin: "B0DLKFK6LR" },
storage_type: "gs",
storage_url: "gs://your-bucket/nimble-results/",
storage_object_name: "my-result"
});
const taskId = response.task.id;
console.log(`Results will be saved to: gs://your-bucket/nimble-results/my-result.json`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/templates/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"template": "amazon_pdp",
"params": {
"asin": "B0DLKFK6LR",
"storage_type": "gs",
"storage_url": "gs://your-bucket/nimble-results/",
"storage_object_name": "my-result"
}
}'
```
When complete, results are written to your bucket as `{task_id}.json` (or `.json.gz` if compressed).
***
## Comparison
| Feature | Polling | Webhooks | Cloud Delivery |
| - | - | - | - |
| **Setup complexity** | None | Requires endpoint | Requires bucket setup |
| **Real-time notifications** | No (you poll) | Yes | No |
| **Automatic storage** | No | No | Yes |
| **Best for** | Simple integrations, testing | Event-driven apps | Data pipelines ETLs |
| **Infrastructure needed** | None | Web server | Cloud storage bucket |
### Combining methods
You can combine delivery methods for redundancy:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Receive webhook AND store in S3
response = nimble.extract.async_(
url="https://www.nimbleway.com",
formats=["html", "markdown"],
callback_url="https://your-server.com/webhooks/nimble",
storage_type="s3",
storage_url="s3://your-bucket/results/"
)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Receive webhook AND store in S3
const response = await nimble.extract.async({
url: "https://www.nimbleway.com",
formats: ["html", "markdown"],
callback_url: "https://your-server.com/webhooks/nimble",
storage_type: "s3",
storage_url: "s3://your-bucket/results/",
});
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"formats": ["html", "markdown"],
"callback_url": "https://your-server.com/webhooks/nimble",
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/"
}'
```
***
## Best Practices
* **Check status first** - Use `/tasks/{id}` before fetching full results -
**Use reasonable intervals** - Poll every 2-5 seconds, not continuously -
**Handle rate limits** - Implement retry logic for 429 responses - **Set
timeouts** - Most tasks complete within seconds to minutes
* **Use HTTPS** - Always use secure endpoints - **Verify authenticity** -
Use custom headers for authentication - **Respond quickly** - Return 200 OK
immediately, process async - **Handle retries** - Nimble retries failed
deliveries
* **Use prefixes** - Organize by date, project, or type - **Enable
compression** - Use `storage_compress: true` for large files - **Set
lifecycle policies** - Auto-delete old files to manage costs - **Use custom
names** - `storage_object_name` for meaningful filenames
## Next Steps
Learn about async extraction options
Deep website crawling with async delivery
Browse available Extract Templates
Understand API rate limit
# Compliance & Security
Source: https://docs.nimbleway.com/nimble-sdk/admin/compliance
Nimble's certifications, data protection posture, and security architecture
Nimble is independently audited and built to meet enterprise data protection requirements.
* **SOC 2 Type II.** Independently audited security controls covering data protection and operational security. Attestation letter available to enterprise customers under NDA via [security@nimbleway.com](mailto:security@nimbleway.com).
* **Zero Data Retention (ZDR).** Request data is processed and not retained beyond fulfilling the request.
* **GDPR, CCPA & data rights.** Compliant with GDPR, UK GDPR, CCPA/CPRA, and Israel's Privacy Protection Law. Data subject requests are handled within regulatory timelines via [privacy@nimbleway.com](mailto:privacy@nimbleway.com).
* **International data transfers.** Covered by the EU Standard Contractual Clauses (2021) and the UK International Data Transfer Addendum.
* **DPA & custom contracts.** Data Processing Agreements, custom MSAs, and security reviews available to meet procurement requirements. Contact [privacy@nimbleway.com](mailto:privacy@nimbleway.com).
* **Security architecture.** Zero-trust access model, encryption in transit and at rest, role-based access control (RBAC), and audit trails across the platform.
Full trust overview: [nimbleway.com/trust](https://www.nimbleway.com/trust)
# Domain Health
Source: https://docs.nimbleway.com/nimble-sdk/admin/domain-health
Check whether Nimble is seeing healthy traffic to specific domains right now
Domain Health reports whether a target site is up or failing, sourced from real request outcomes across Search, SERP, ecommerce, and Extract traffic. Use it to confirm a failure is the domain's fault, not Nimble's, before opening a support ticket.
## Quick Start
### Example Request
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.domain_health.check(
domains=["walmart.com", "hannaford.com"]
)
for domain in result.domains:
print(f"{domain.domain}: {domain.status} ({domain.success_rate})")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.domainHealth.check({
domains: ["walmart.com", "hannaford.com"],
});
result.domains.forEach(domain => {
console.log(`${domain.domain}: ${domain.status} (${domain.success_rate})`);
});
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
result, err := client.DomainHealth.Check(context.Background(), nimble.DomainHealthCheckParams{
Domains: []string{"walmart.com", "hannaford.com"},
})
if err != nil {
panic(err)
}
for _, domain := range result.Domains {
fmt.Printf("%s: %s (%v)\n", domain.Domain, domain.Status, domain.SuccessRate)
}
}
```
```bash CLI theme={"system"}
nimble domain-health check \
--domains walmart.com,hannaford.com
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v1/domain-health/check' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"domains": ["walmart.com", "hannaford.com"]
}'
```
### Example Response
```json theme={"system"}
{
"domains": [
{
"domain": "walmart.com",
"status": "up",
"success_rate": 0.94,
"consecutive_bad_windows": 0,
"history": [
{
"hours_ago": 0,
"window_start": "2026-08-24T11:00:00Z",
"window_end": "2026-08-24T12:00:00Z",
"status": "up",
"success_rate": 0.95
},
{"...": "5 more hourly buckets"}
]
},
{
"domain": "hannaford.com",
"status": "down",
"success_rate": 0.0,
"consecutive_bad_windows": 6,
"history": [
{"...": "6 hourly buckets"}
]
}
]
}
```
## Parameters
Domains to check, 1–100 per request. Pass the bare domain (e.g. `walmart.com`), not a full URL.
## Response
Each entry in `domains` reports the live status plus a 6-hour trend:
| Field | Description |
| - | - |
| `domain` | The domain checked |
| `status` | `up`, `degraded`, `down`, or `unknown` |
| `success_rate` | Success rate over the live detection window |
| `consecutive_bad_windows` | Consecutive 10-minute windows below the success threshold |
| `history` | 6 hourly buckets, most recent completed hour first, each with `hours_ago`, `window_start`, `window_end`, `status`, `success_rate` |
### Status values
| Status | Meaning |
| - | - |
| `up` | Latest window's success rate is at or above the healthy threshold |
| `degraded` | 1–2 consecutive bad windows |
| `down` | 3 or more consecutive bad windows |
| `unknown` | Fewer than 10 samples for that domain in the lookback window |
Status comes from real request outcomes across Search, SERP, ecommerce, and Extract traffic, not a synthetic check. A domain with no recent traffic on your account returns `unknown`.
## Errors
| Status | Meaning |
| - | - |
| `400` | `domains` is empty or has more than 100 entries |
| `401` | Missing or invalid API key |
| `429` | Rate limited |
| `503` | Health data temporarily unavailable. Never reported as `up` by default |
Typical response time is under 2 seconds (p95) for 1-100 domains.
## Next steps
Live, product-wide status for Search, SERP, ecommerce, and Extract
Full request and response schema
# Legacy Docs
Source: https://docs.nimbleway.com/nimble-sdk/admin/legacy-docs
# Pricing
Source: https://docs.nimbleway.com/nimble-sdk/admin/pricing
Flexible usage-based pricing for Nimble SDK
Nimble offers two ways to access the platform: **API pricing** for direct, per-request access, and **Data Services plans** for managed, production-scale workloads.
**Free Trial:** Get started with 5,000 free web pages. No credit card
required. [Start Building Now](https://online.nimbleway.com/signup)
***
## API Pricing
Use any Nimble API without a commitment. Only successful requests are charged.
| Product Line | Price |
| - | - |
| Extraction Tools | \$1.00 / 1K URLs |
| Extract file parsing, light (add-on) | \$2.50 / 1K pages |
| Extract file parsing, pro (add-on) | \$7.50 / 1K pages |
| Extract Templates | \$3.00 / 1K runs |
| Media | \$2.00 / GB |
| Enterprise deployments | Custom |
Extract file parsing is billed per page parsed, on top of the base Extraction Tools price. No parsing charge applies when a PDF is rejected for exceeding `max_pages`. See [Extract](/nimble-sdk/web-tools/extract/quickstart).
### Search
Price depends on `search_depth`. Only successful requests are charged.
| Depth | Price |
| - | - |
| lite | \$1.10 / 1K inputs |
| standard | \$5.00 / 1K inputs |
Adding `full_content: true` adds a \$1.00 / 1K URLs surcharge, on top of the
depth price above, for each page scraped in real time.
### Agent
Pick an effort level for each task. Every level has one flat price, billed only for the tasks run.
| Effort | Description | Duration | Price |
| - | - | - | - |
| Low | Quick lookups and fact checks | 10-30 sec | \$0.025 / task |
| Med | Multi-source research | 1-3 min | \$0.10 / task |
| High | Deep cross-source research | 5-15 min | \$0.50 / task |
| X-High | Heavy multi-step jobs | 15-30 min | \$2.00 / task |
| Max | Unlimited effort, you set the budget per task | 30 min-hours | Custom |
Effort level sets how much agent infrastructure Nimble provisions: compute, retrieval, storage, and how long the agent runs. None of it appears on the bill. Billing is per task, at the effort price chosen.
### Proxy API
| Product | PAYG Price |
| - | - |
| Residential Proxy | \$5.30 / GB |
### Custom Rates
Custom API pricing for high-scale workloads. Includes:
* **Volume discounts:** lower per-request rates at scale
* **Multi-year pricing protection:** rate locks and long-term discounts
* **Product bundling discounts:** combine APIs for better rates
* **Custom concurrent sessions:** beyond standard limits
* **Dedicated support** and SLA options
To discuss custom API pricing for your use case, please [Contact
Sales](https://nimbleway.com/contact-general/)
***
## Data Services Plans
Managed plans for teams running agents and data pipelines at scale. Billed annually.
| | **Startup** | **Scale** | **Professional** | **Enterprise** |
| - | - | - | - | - |
| **Monthly Price** | \$2,500/mo | \$7,000/mo | \$15,000/mo | Custom |
| **Concurrent Agents** | 5 | 10 | 20 | Unlimited |
| **Credits** | 350K pages | 1.2M pages | 3M pages | Custom |
| **Storage** | 7 days | 30 days | 90 days | Custom |
* 350,000 web page credits
* 5 concurrent agents
* 7 days data storage
* Custom agent ETL
* MCP integration
* 1,200,000 web page credits
* 10 concurrent agents
* 30 days data storage
* Custom agent ETL
* MCP integration
* Localized Agents
* 3,000,000 web page credits
* 20 concurrent agents
* 90 days data storage
* Custom agent ETL
* MCP integration
* Localized Agents
* Custom workflow
Everything in Professional, plus:
* Unlimited concurrent Agents
* Advanced security features
* Custom data storage
* Advanced SLAs
[Contact Sales](https://nimbleway.com/contact-general/) to discuss your requirements.
***
## Billing FAQ
Pay-as-you-go usage is billed monthly by credit card. Data Services plans
are billed annually. Only successful requests count toward your usage.
Additional usage is billed at pay-as-you-go rates on your next invoice.
Yes. Plan changes take effect on your next billing cycle.
Credits roll over within your billing cycle but expire at the end of each
month.
[Contact our sales team](https://nimbleway.com/contact-general/) for custom
rates based on your volume and requirements.
***
## Get Started
Get 5,000 free pages to test the platform
Discuss Data Services plans or custom pricing
# Rate Limits
Source: https://docs.nimbleway.com/nimble-sdk/admin/rate-limits
Technical specifications, rate limits, and API constraints
Understanding API limitations and specifications helps you optimize performance, plan capacity, and avoid throttling. This guide covers rate limits, driver capabilities, request constraints, and best practices.
## Overview
Nimble's API implements tiered specifications based on:
* **Driver selection**: Different drivers have different rate limits
* **Request complexity**: Complex operations may have additional constraints
* **Resource usage**: Fair usage policies ensure platform stability
## Drivers
Nimble offers different drivers optimized for various use cases:
| **Driver** | **Description** | **Best For** |
| - | - | - |
| `vx6` | HTTP requests without rendering | Static pages, simple HTML |
| `vx8` | JavaScript rendering | Dynamic content, AJAX |
| `vx10` | Stealth browser mode | Protected sites, complex sites |
Each driver has different pricing and rate limits. Higher-tier drivers cost more per request.
## Rate Limits
### Default rate limits by driver
All accounts have the following default rate limits:
| **Driver** | **Rate Limit** |
| - | - |
| `vx6`, `vx8`, `vx10` | 83 QPS (5,000 QPM) |
**Need higher limits?** Reach out to your CS or open a via a [Support Ticket](https://portal.usepylon.com/nimble) to discuss custom rate limits based on your requirements.
### Extract Template generation
Custom Extract Template generation has a separate daily limit:
| **Endpoint** | **Limit** |
| - | - |
| `POST /v2/extract/templates/generations` | 100 generations per customer / day |
This limit applies across all generation methods (API, CLI, SDK, and the Nimble plugin). It covers both new template creation and refinement of an existing template. See [Extract Templates](/nimble-sdk/web-tools/extract/template) for details.
### Response headers
Every response includes metadata headers:
```
ratelimit-limit: 20
ratelimit-remaining: 15
x-nimble-request-id: 25b69007-21e0-4a6b-8cb8-c3816bfd6676
x-task-id: 8e8cfde8-345b-42b8-b3e2-0c61eb11e00f
```
Use these headers to:
* Monitor rate limit usage
* Track request IDs for debugging
* Optimize driver selection
* Measure performance
### Handling rate limits
When you exceed rate limits, the API returns a 429 status code:
```json theme={"system"}
{
"status": "failed",
"msg": "Rate limit exceeded"
}
```
**Best practices:**
* Implement exponential backoff on 429 responses
* Use the `retry_after` value to schedule next request
* Monitor rate limit headers in responses
* Batch requests when possible
* Use lower-tier drivers when sufficient
## HTTP status codes
### Success codes
| Code | Status | Description |
| :- | :- | :- |
| **200** | OK | Request succeeded and data was returned successfully |
### Client error codes
| Code | Status | Description |
| :- | :- | :- |
| **400** | Bad Request | Invalid URL, malformed parameters, or missing required fields |
| **401** | Unauthorized | Account doesn't exist, invalid API key, or missing credentials |
| **402** | Payment Required | No budget, limit reached, trial expired, or trial quota finished |
| **403** | Forbidden | Account blocked or not activated |
| **429** | Too Many Requests | Rate limit exceeded for your plan |
### Server error codes
| Code | Status | Description |
| :- | :- | :- |
| **500** | Internal Server Error | Unexpected server-side error occurred |
| **501** | Not Implemented | Proxy service encountered an error |
| **555** | Request Timeout | Request exceeded maximum execution time |
Nimble automatically retries failed requests before returning a final failure status (500)
## Blocked Domains
The following domains are blocked across all Web APIs and Proxy services. Requests targeting these domains will be rejected.
* `*.paypal.com`
* `account.sonyentertainmentnetwork.com`
* `acopic.click`
* `adskpak.com`
* `amctheatres.com`
* `andromedabee.com`
* `antliabee.co.uk`
* `api.groupon.com`
* `api.stripe.com`
* `api.tyrads.com`
* `apusbee.co.uk`
* `auth.api.sonyentertainmentnetwork.com`
* `bellarmine.click`
* `caixa.gov.br`
* `callofduty.com`
* `canal-plus.com`
* `clubmium.com`
* `clubmium.net`
* `couponcabin.com`
* `crunchyroll.com`
* `dazn.com`
* `directv.com`
* `ea.com`
* `epicgames.com`
* `fustian.click`
* `gomobile.it`
* `googlecm.hit.gemius.pl`
* `hbo.com`
* `hitspro.net`
* `honeyga.in`
* `honeygain.com`
* `honeygain.money`
* `hotstar.com`
* `iplogger.org`
* `kayak.com`
* `mojang.com`
* `mte.gov.br`
* `music-box.mobi`
* `mybip.it`
* `myy.io`
* `native.np.ac.playstation.net`
* `networkcontroller.net`
* `ocmey.com`
* `onevanilla.com`
* `ordo.link`
* `pagseguro.com`
* `paypal.com`
* `playstation.com`
* `playstation.net`
* `plex.tv`
* `powerbrowsing.online`
* `revenuevelocity.eu`
* `roblox.com`
* `runescape.com`
* `seasyvendas.com`
* `sky.com`
* `sport4life.mobi`
* `spotify.com`
* `ssfcu.com`
* `steamcommunity.com`
* `steampowered.com`
* `torrentdownloads.me`
* `ttvnw.net`
* `tyrads.com`
* `tyrrewards.com`
* `ubi.com`
* `ubisoft.com`
* `unrealengine.com`
* `usps.com`
* `valuedopinions.com`
* `vrv.co`
* `wellsfargo.com`
* `www.ltv-mob.com`
* `yogini.top`
* `zelator.in`
# Gallery
Source: https://docs.nimbleway.com/nimble-sdk/agentic/agent-gallery
Browse Nimble's catalog of pre-built Extract Templates
The **Gallery** is Nimble's catalog of pre-built Extract Templates for popular websites. These templates are maintained by Nimble 24/7 and auto-heal when sites change.
**Don't see the site you need?** Use [Extract](/nimble-sdk/web-tools/extract/quickstart) to pull structured data from any URL.
Learn how to call templates from your code, no CSS selectors required
Let Claude Code or Cursor find the right template for you automatically
Explore pre-built templates with previews, examples, and interactive
documentation.
### When to Use
Use template discovery when you need to:
* **Browse available templates** - See what platforms are supported
* **Filter by vertical** - Find templates for specific industries (e-commerce, search, social)
* **Inspect template details** - Understand input parameters and output schema
* **Check capabilities** - Verify localization and pagination support
### Two Ways to Explore
Prefer a visual interface? Browse templates with previews, live demos, and
exportable code snippets at the [visual
gallery](https://online.nimbleway.com/pipeline-gallery) in Nimble Platform.
***
## List Templates
Browse and search the template catalog.
`GET https://sdk.nimbleway.com/v2/extract/templates`
### Parameters
| Parameter | Type | Description | Required |
| - | - | - | - |
| `privacy` | Enum | Filter by privacy level (`all`, `public`, `private`) | No |
| `search` | String | Filter templates by keyword. | No |
| `offset` | Integer | Pagination offset (default: `0`) | No |
| `limit` | Integer | Number of results per page (default: `100`) | No |
### Response Schema
Each template in the response contains:
| Field | Type | Description |
| - | - | - |
| `name` | string | Unique template identifier (use this in API calls) |
| `display_name` | string | Human-readable template name |
| `description` | string | What the template extracts |
| `is_public` | boolean | Whether the template is publicly available |
| `managed_by` | string | Who maintains this template: `nimble` (public), `community` (public), or `self` (private) |
| `vertical` | string | Industry vertical (e.g., "Ecommerce", "Search") |
| `entity_type` | string | Page type (e.g., "Product Detail Page", "SERP") |
| `domain` | string | Target website domain |
### Usage Examples
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# List all public templates
templates = nimble.extract.templates.list(privacy="public")
for template in templates:
print(f"{template.name}: {template.display_name}")
print(f" Domain: {template.domain}")
print(f" Vertical: {template.vertical}")
print(f" Type: {template.entity_type}")
```
```javascript Node theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// List all public templates
const templates = await nimble.extract.templates.list({ privacy: "public" });
templates.forEach((template) => {
console.log(`${template.name}: ${template.display_name}`);
console.log(` Domain: ${template.domain}`);
console.log(` Vertical: ${template.vertical}`);
console.log(` Type: ${template.entity_type}`);
});
```
```bash cURL theme={"system"}
curl -X GET 'https://sdk.nimbleway.com/v2/extract/templates?privacy=public' \
-H 'Authorization: Bearer YOUR-API-KEY'
```
### Response Example
```json theme={"system"}
[
{
"name": "amazon_pdp",
"is_public": true,
"display_name": "Amazon Product Page",
"description": "Extract structured data from Amazon product detail pages including pricing, reviews, specifications, and availability.",
"vertical": "Ecommerce",
"entity_type": "Product Detail Page (PDP)",
"domain": "www.amazon.com"
},
{
"name": "amazon_serp",
"is_public": true,
"display_name": "Amazon Search Results",
"description": "Extract product listings from Amazon search results pages based on a keyword query.",
"vertical": "Ecommerce",
"entity_type": "Search Engine Results Page (SERP)",
"domain": "www.amazon.com"
},
{
"name": "walmart_pdp",
"is_public": true,
"display_name": "Walmart Product Page",
"description": "Extract structured data from Walmart product detail pages including pricing, reviews, and variants.",
"vertical": "Ecommerce",
"entity_type": "Product Detail Page (PDP)",
"domain": "www.walmart.com"
},
{
"name": "google_search",
"is_public": true,
"display_name": "Google Search Results",
"description": "Extract structured data from Google Search Results Pages (SERPs) including organic results, ads, and featured snippets.",
"vertical": "Search Engine",
"entity_type": "Search Engine Results Page (SERP)",
"domain": "www.google.com"
}
]
```
***
## Get Template Details
Get comprehensive information about a specific template, including input parameters and output schema.
```GET https://sdk.nimbleway.com/v2/extract/templates/{extract_template_name} theme={"system"}
```
### Parameters
| Parameter | Type | Description | Required |
| - | - | - | - |
| `extract_template_name` | string | The template name (e.g., `amazon_pdp`) | Yes |
### Response Schema
| Field | Type | Description |
| - | - | - |
| `name` | string | Unique template identifier |
| `display_name` | string | Human-readable template name |
| `description` | string | What the template extracts |
| `is_public` | boolean | Whether the template is publicly available |
| `managed_by` | string | Who maintains this template: `nimble` (public), `community` (public), or `self` (private) |
| `vertical` | string | Industry vertical |
| `entity_type` | string | Page type |
| `domain` | string | Target website domain |
| `input_properties` | array | Input parameters for the template (see below) |
| `output_schema` | object | JSON schema describing output fields |
| `feature_flags` | object | Supported capabilities (localization, pagination) |
#### Input Properties
Each item in `input_properties` contains:
| Field | Type | Description |
| - | - | - |
| `name` | string | Parameter name |
| `type` | string | Data type (string, number, boolean) |
| `description` | string | What the parameter does |
| `required` | boolean | Whether the parameter is required |
| `default` | string | Default value if not provided |
| `examples` | array | Example values |
| `rules` | array | Validation rules |
| `is_localization_param` | boolean | Whether the parameter controls localization |
| `is_pagination_param` | boolean | Whether the parameter controls pagination |
#### Feature Flags
| Flag | Type | Description |
| - | - | - |
| `is_localization_supported` | boolean | Supports ZIP code for location-specific data |
| `is_pagination_supported` | boolean | Supports paginated results |
### Usage Examples
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Get template details
template = nimble.extract.templates.get("amazon_pdp")
print(f"Template: {template.display_name}")
print(f"Description: {template.description}")
print(f"Domain: {template.domain}")
# Check feature flags
flags = template.feature_flags
print(f"\nCapabilities:")
print(f" Localization: {'Yes' if flags.is_localization_supported else 'No'}")
print(f" Pagination: {'Yes' if flags.is_pagination_supported else 'No'}")
# List input parameters
print(f"\nInput Parameters:")
for param in template.input_properties:
required = "required" if param.required else "optional"
print(f" {param.name} ({param.type}) - {required}")
print(f" {param.description}")
if param.examples:
print(f" Examples: {', '.join(param.examples)}")
```
```javascript Node theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Get template details
const template = await nimble.extract.templates.get("amazon_pdp");
console.log(`Template: ${template.display_name}`);
console.log(`Description: ${template.description}`);
console.log(`Domain: ${template.domain}`);
// Check feature flags
const flags = template.feature_flags;
console.log("\nCapabilities:");
console.log(
` Localization: ${flags.is_localization_supported ? "Yes" : "No"}`,
);
console.log(` Pagination: ${flags.is_pagination_supported ? "Yes" : "No"}`);
// List input parameters
console.log("\nInput Parameters:");
template.input_properties.forEach((param) => {
const required = param.required ? "required" : "optional";
console.log(` ${param.name} (${param.type}) - ${required}`);
console.log(` ${param.description}`);
if (param.examples?.length) {
console.log(` Examples: ${param.examples.join(", ")}`);
}
});
```
```bash cURL theme={"system"}
curl -X GET 'https://sdk.nimbleway.com/v2/extract/templates/amazon_pdp' \
-H 'Authorization: Bearer YOUR-API-KEY'
```
### Response Example
```json theme={"system"}
{
"name": "amazon_pdp",
"is_public": true,
"display_name": "Amazon Product Page",
"description": "Extract structured data from Amazon product detail pages including pricing, reviews, specifications, variants, and availability.",
"vertical": "Ecommerce",
"entity_type": "Product Detail Page (PDP)",
"domain": "www.amazon.com",
"input_properties": [
{
"name": "asin",
"required": true,
"type": "string",
"description": "Amazon Standard Identification Number (ASIN) - the unique 10-character product identifier",
"rules": ["Must be exactly 10 alphanumeric characters"],
"examples": ["B08N5WRWNW", "B0DLKFK6LR"],
"default": null
},
{
"name": "zip_code",
"required": false,
"type": "string",
"description": "ZIP code for location-specific pricing and availability",
"rules": ["5-digit US ZIP code"],
"examples": ["90210", "10001"],
"default": "90210"
}
],
"output_schema": {
"asin": { "type": "string", "description": "Product ASIN" },
"product_title": { "type": "string", "description": "Full product name" },
"brand": { "type": "string", "description": "Product brand" },
"web_price": { "type": "number", "description": "Current selling price" },
"list_price": {
"type": "number",
"description": "Original price before discount"
},
"average_of_reviews": {
"type": "number",
"description": "Average rating (0-5)"
},
"number_of_reviews": {
"type": "number",
"description": "Total review count"
},
"availability": { "type": "boolean", "description": "Whether in stock" },
"image_url": { "type": "string", "description": "Main product image URL" }
},
"feature_flags": {
"is_localization_supported": true,
"is_pagination_supported": false
}
}
```
***
## Available Templates by Vertical
### E-commerce
| Template | Display Name | Domain | Localization | Pagination |
| - | - | - | - | - |
| `amazon_pdp` | Amazon Product Page | [www.amazon.com](http://www.amazon.com) | Yes | No |
| `amazon_serp` | Amazon Search | [www.amazon.com](http://www.amazon.com) | Yes | Yes |
| `amazon_category` | Amazon Category | [www.amazon.com](http://www.amazon.com) | No | Yes |
| `walmart_pdp` | Walmart Product Page | [www.walmart.com](http://www.walmart.com) | Yes | No |
| `walmart_search` | Walmart Search | [www.walmart.com](http://www.walmart.com) | Yes | Yes |
| `target_pdp` | Target Product Page | [www.target.com](http://www.target.com) | Yes | No |
| `best_buy_pdp` | Best Buy Product | [www.bestbuy.com](http://www.bestbuy.com) | Yes | No |
| `home_depot_pdp` | Home Depot Product | [www.homedepot.com](http://www.homedepot.com) | Yes | No |
### Search Engines
| Template | Display Name | Domain | Localization | Pagination |
| - | - | - | - | - |
| `google_search` | Google Search Results | [www.google.com](http://www.google.com) | Yes | Yes |
| `google_maps_search` | Google Maps Search | maps.google.com | Yes | Yes |
| `google_search_aio` | Google AI Overview | [www.google.com](http://www.google.com) | Yes | No |
### Social Media
| Template | Display Name | Domain | Localization | Pagination |
| - | - | - | - | - |
| `tiktok_account` | TikTok Account | [www.tiktok.com](http://www.tiktok.com) | No | Yes |
| `facebook_page` | Facebook Page | [www.facebook.com](http://www.facebook.com) | No | No |
| `youtube_shorts` | YouTube Shorts | [www.youtube.com](http://www.youtube.com) | No | Yes |
### LLM Platforms
| Template | Display Name | Domain | Localization | Pagination |
| - | - | - | - | - |
| `chatgpt` | ChatGPT Responses | chatgpt.com | No | No |
| `gemini` | Gemini Responses | gemini.google.com | No | No |
| `perplexity` | Perplexity | [www.perplexity.ai](http://www.perplexity.ai) | No | No |
| `grok` | Grok Responses | grok.com | No | No |
***
## Common Patterns
### Search Templates by Keyword
Use the `search` query parameter to find templates matching a term across `display_name`, `vertical`, and `domain`:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
templates = nimble.extract.templates.list(search="amazon")
for template in templates:
print(f"{template.name}: {template.display_name} ({template.domain})")
```
```javascript Node theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const templates = await nimble.extract.templates.list({ search: "amazon" });
templates.forEach((template) => {
console.log(`${template.name}: ${template.display_name} (${template.domain})`);
});
```
```bash cURL theme={"system"}
curl -X GET 'https://sdk.nimbleway.com/v2/extract/templates?search=amazon' \
-H 'Authorization: Bearer YOUR-API-KEY'
```
### Filter Templates by Vertical
Find all templates for a specific industry:
```python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Get all templates
templates = nimble.extract.templates.list(privacy="public")
# Filter by vertical
ecommerce_templates = [
template for template in templates
if template.vertical == 'Ecommerce'
]
print(f"Found {len(ecommerce_templates)} e-commerce templates:")
for template in ecommerce_templates:
print(f" {template.name}: {template.display_name} ({template.domain})")
```
### Check Localization Support
Verify a template supports location-specific data:
```python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
template = nimble.extract.templates.get("amazon_pdp")
if template.feature_flags.is_localization_supported:
print("This template supports localization!")
print("Pass zip_code parameter for location-specific pricing.")
else:
print("This template does not support localization.")
```
### Get Required Parameters
Find which parameters are required before running a template:
```python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
template = nimble.extract.templates.get("amazon_pdp")
required_params = [
p for p in template.input_properties
if p.required
]
optional_params = [
p for p in template.input_properties
if not p.required
]
print("Required parameters:")
for p in required_params:
examples = ', '.join(p.examples) if p.examples else ''
print(f" {p.name}: {p.description}")
if examples:
print(f" Examples: {examples}")
print("\nOptional parameters:")
for p in optional_params:
default = p.default if p.default else 'none'
print(f" {p.name}: {p.description} (default: {default})")
```
### Validate Parameters Before Calling
Check parameters match the expected schema:
```python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
def validate_params(template_name, params):
"""Validate params against template input_properties"""
template = nimble.extract.templates.get(template_name)
errors = []
# Build lookup of input properties
props = {p.name: p for p in template.input_properties}
# Check required fields
for prop_name, prop in props.items():
if prop.required and prop_name not in params:
errors.append(f"Missing required parameter: {prop_name}")
# Check for unknown parameters
for param_name in params:
if param_name not in props:
errors.append(f"Unknown parameter: {param_name}")
return errors
# Validate before calling
errors = validate_params("amazon_pdp", {"asin": "B08N5WRWNW"})
if errors:
print("Validation errors:")
for error in errors:
print(f" - {error}")
else:
print("Parameters are valid!")
```
***
## Next Steps
Pull structured data from any URL with full control
Use Nimble in Claude Code or Cursor
Learn how to call templates in your code
Explore endpoints and response schemas for the Extract Templates API
# Extract Jobs
Source: https://docs.nimbleway.com/nimble-sdk/agentic/jobs
Run any Extract Template on a schedule or on demand — at scale, hands-off.
Nimble **Jobs** turn any Extract Template into a managed, repeatable workload. Point a job at a template, hand it a list of inputs (CSV, Parquet, or JSON), pick a schedule — and Nimble runs the template across every input on cadence, tracks every execution, and delivers structured results to you.
Jobs are the right tool when you need to run a template across **thousands or millions of inputs**, on a recurring basis, without writing orchestration code. Think daily price refreshes across your full SKU catalog, hourly SERP monitoring across thousands of keywords, or weekly competitor sweeps across every product page you care about.
Create and manage jobs either in the [Nimble Platform](https://online.nimbleway.com/jobs) or programmatically through the [Jobs API](/api-reference/jobs/create-job) — both read and write the same jobs, so you can switch between them at any time.
Create your first job in the Nimble Platform — no coding required
Drive jobs from code, the terminal, or CI/CD pipelines
Pick a pre-built template or bring your own custom template
## Quick Start
Create a job, trigger a run, wait for it to finish, and download the results. Every step maps to a [Jobs API](/api-reference/jobs/create-job) endpoint.
### Example Request
```python Python theme={"system"}
import time
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# 1. Create a job — bind a template to inputs, a destination, and a schedule
job = nimble.jobs.create(
name="daily_amazon_top_skus",
extract_template_name="amazon_pdp",
schedule={"cron": "0 7 * * *", "enabled": True},
inputs={
"type": "inline",
"data": [{"asin": "B08N5WRWNW"}, {"asin": "B0DLKFK6LR"}],
},
destination={
"type": "s3",
"path": "s3://my-bucket/amazon-skus/",
"format": "parquet",
},
)
# 2. Trigger a run now (on top of any schedule)
run = nimble.jobs.runs.create(job.id)
# 3. Poll until the run reaches a terminal state
status = run.status
while status in ("PENDING", "RUNNING"):
time.sleep(10)
status = nimble.jobs.runs.get(run.id).status
# 4. Download the assembled results
artifacts = nimble.jobs.runs.artifacts.list(run.id)
download = nimble.jobs.runs.artifacts.download_url(
int(artifacts.items[0].id),
run_id=run.id,
)
print(download.url)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// 1. Create a job — bind a template to inputs, a destination, and a schedule
const job = await nimble.jobs.create({
name: "daily_amazon_top_skus",
extract_template_name: "amazon_pdp",
schedule: { cron: "0 7 * * *", enabled: true },
inputs: {
type: "inline",
data: [{ asin: "B08N5WRWNW" }, { asin: "B0DLKFK6LR" }],
},
destination: {
type: "s3",
path: "s3://my-bucket/amazon-skus/",
format: "parquet",
},
});
// 2. Trigger a run now (on top of any schedule)
const run = await nimble.jobs.runs.create(job.id);
// 3. Poll until the run reaches a terminal state
let status: string = run.status;
while (status === "PENDING" || status === "RUNNING") {
await new Promise((r) => setTimeout(r, 10000));
status = (await nimble.jobs.runs.get(run.id)).status;
}
// 4. Download the assembled results
const artifacts = await nimble.jobs.runs.artifacts.list(run.id);
const download = await nimble.jobs.runs.artifacts.downloadURL(
Number(artifacts.items[0].id),
{ run_id: run.id },
);
console.log(download.url);
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
"strconv"
"time"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
ctx := context.Background()
// 1. Create a job — bind a template to inputs, a destination, and a schedule
job, err := client.Jobs.New(ctx, nimble.JobNewParams{
Name: "daily_amazon_top_skus",
ExtractTemplateName: "amazon_pdp",
Schedule: nimble.JobNewParamsSchedule{Cron: "0 7 * * *", Enabled: true},
Inputs: nimble.JobNewParamsInputs{
Type: "inline",
Data: []map[string]any{{"asin": "B08N5WRWNW"}, {"asin": "B0DLKFK6LR"}},
},
Destination: nimble.JobNewParamsDestination{
Type: "s3",
Path: "s3://my-bucket/amazon-skus/",
Format: "parquet",
},
})
if err != nil {
panic(err)
}
// 2. Trigger a run now (on top of any schedule)
run, err := client.Jobs.Runs.New(ctx, job.ID)
if err != nil {
panic(err)
}
// 3. Poll until the run reaches a terminal state
status := string(run.Status)
for status == "PENDING" || status == "RUNNING" {
time.Sleep(10 * time.Second)
polled, err := client.Jobs.Runs.Get(ctx, run.ID)
if err != nil {
panic(err)
}
status = string(polled.Status)
}
// 4. Download the assembled results
artifacts, err := client.Jobs.Runs.Artifacts.List(ctx, run.ID)
if err != nil {
panic(err)
}
artifactID, _ := strconv.ParseInt(artifacts.Items[0].ID, 10, 64)
download, err := client.Jobs.Runs.Artifacts.DownloadURL(ctx, artifactID,
nimble.JobRunArtifactDownloadURLParams{RunID: run.ID})
if err != nil {
panic(err)
}
fmt.Println(download.URL)
}
```
```bash CLI theme={"system"}
# 1. Create a job
JOB_ID=$(nimble jobs create --name daily_amazon_top_skus --extract-template-name amazon_pdp --schedule '{cron: "0 7 * * *", enabled: true}' --inputs '{type: inline, data: [{asin: B08N5WRWNW}, {asin: B0DLKFK6LR}]}' --destination '{type: s3, path: "s3://my-bucket/amazon-skus/", format: parquet}' | jq -r .id)
# 2. Trigger a run now (on top of any schedule)
RUN_ID=$(nimble jobs:runs create --job-id "$JOB_ID" | jq -r .id)
# 3. Poll until the run reaches a terminal state
nimble jobs:runs get --run-id "$RUN_ID"
# 4. Download the assembled results
nimble jobs:runs:artifacts list --run-id "$RUN_ID"
nimble jobs:runs:artifacts download-url --run-id "$RUN_ID" --artifact-id 7
```
```bash cURL theme={"system"}
# 1. Create a job
curl -X POST 'https://sdk.nimbleway.com/v2/jobs' \
--header 'Authorization: Bearer YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "daily_amazon_top_skus",
"extract_template_name": "amazon_pdp",
"schedule": {"cron": "0 7 * * *", "enabled": true},
"inputs": {
"type": "inline",
"data": [{"asin": "B08N5WRWNW"}, {"asin": "B0DLKFK6LR"}]
},
"destination": {
"type": "s3",
"path": "s3://my-bucket/amazon-skus/",
"format": "parquet"
}
}'
# 2. Trigger a run (returns a run with id "run_")
curl -X POST 'https://sdk.nimbleway.com/v2/jobs/job_42/runs' \
--header 'Authorization: Bearer YOUR-API-KEY'
# 3. Poll the run until status is SUCCESS
curl -X GET 'https://sdk.nimbleway.com/v2/jobs/runs/run_1204' \
--header 'Authorization: Bearer YOUR-API-KEY'
# 4. Get a presigned download URL for an artifact
curl -X GET 'https://sdk.nimbleway.com/v2/jobs/runs/run_1204/artifacts/7/download-url' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
### Example Response
Creating a job returns a [`Job`](/api-reference/jobs/create-job) resource. Use its `id` (formatted `job_`) for every follow-up call.
```json theme={"system"}
{
"id": "job_42",
"name": "daily_amazon_top_skus",
"extract_template_name": "amazon_pdp",
"schedule": { "cron": "0 7 * * *", "enabled": true },
"inputs": { "type": "inline", "data": [{ "asin": "B08N5WRWNW" }] },
"destination": {
"type": "s3",
"path": "s3://my-bucket/amazon-skus/",
"format": "parquet"
},
"last_run_at": null,
"last_run_status": null,
"created_at": "2026-06-17T09:00:00Z",
"updated_at": "2026-06-17T09:00:00Z"
}
```
## How it works
A job binds together a **template**, an **input set**, a **destination**, and a **trigger** (manual or scheduled). Once created, a job is a stable, named workload you can run again and again with the same configuration.
Each execution of a job is a **run**. A run reads every row from your input set and dispatches one template invocation per row in parallel. Nimble handles concurrency, retries, anti-bot handling, and result aggregation — you don't manage workers, queues, or rate limits.
When the run completes, Nimble assembles the per-row results into **artifacts** — the input set, the valid output rows, and the invalid output rows — downloadable as JSON, CSV, or Parquet, with success rate, completeness, and error samples surfaced per run.
For scheduled jobs, Nimble triggers a new run on every tick of the cadence. Each run is independent — same configuration, fresh data — so monitoring dashboards, downstream pipelines, and time-series comparisons stay current without any manual work.
## Manage Jobs programmatically
Everything you can do in the UI is available through the [Jobs API](/api-reference/jobs/create-job) — create, list, update, and delete jobs; trigger and cancel runs; and list, preview, or download artifacts.
Full endpoint, parameter, and schema reference for every Jobs operation
Wire a Job's input source and destination to your S3 or Databricks storage
All requests use Bearer-token authentication against the base URL `https://sdk.nimbleway.com`:
```bash theme={"system"}
Authorization: Bearer YOUR-API-KEY
```
The most common endpoints:
| Action | Endpoint |
| - | - |
| Create a job | `POST /v2/jobs` |
| List jobs | `GET /v2/jobs` |
| Get / update / delete a job | `GET` · `PATCH` · `DELETE /v2/jobs/{job_id}` |
| Trigger a run | `POST /v2/jobs/{job_id}/runs` |
| List runs / get a run | `GET /v2/jobs/{job_id}/runs` · `GET /v2/jobs/runs/{run_id}` |
| Cancel a run | `POST /v2/jobs/runs/{run_id}/cancel` |
| List / preview / download artifacts | `GET /v2/jobs/runs/{run_id}/artifacts...` |
Editing a job mid-run is safe — updates take effect on the next run, and runs already in progress finish on the old configuration. Calling **Trigger Run** on a scheduled job kicks off an extra run without affecting the cron cadence.
## Using the Nimble Platform
The [Jobs page](https://online.nimbleway.com/jobs) provides the same capabilities with no code. Click **Create Job** to get started.
Go to [online.nimbleway.com/jobs](https://online.nimbleway.com/jobs) and click **Create Job**.
Choose any pre-built template (e.g., `amazon_pdp`, `google_search`, `chatgpt`) or one of your custom templates.
Upload a CSV/Parquet file or paste a JSON array of inputs — one entry per template run.
Choose **Manual Trigger Only** for on-demand runs, or **Schedule** to run every day, week, month, at a specific time, or on a custom cron expression.
Trigger the first run manually or wait for the schedule. Download structured results as JSON, CSV, or Parquet from the run page.
### Creating a Job
The **Create Job** panel has five sections. Only Job Title, Template, and Inputs are required — everything else has sensible defaults.
#### Job Title
A human-readable name for the job. This is what you'll see in the jobs list, in run logs, and in delivered file names. Pick something descriptive — e.g., `daily-amazon-top-1000-skus` or `hourly-google-serp-priority-keywords`.
#### Template
Select the Extract Template that the job will run. Any template available in your workspace can be used:
* **Pre-built templates** from the [Gallery](/nimble-sdk/agentic/agent-gallery) (e.g., `amazon_pdp`, `google_search`, `chatgpt`, `tiktok_account`).
* **Custom templates** you've built in [Ask Nimble](https://online.nimbleway.com/workflow-builder).
A job runs exactly one template. To run multiple templates over the same input set, create one job per template.
#### Inputs
The input set defines **what the template runs against**. Each entry in the input set becomes one template invocation in every run. Jobs support four input modes:
Upload a **CSV** or **Parquet** file. Each row is one template invocation; column names must match the template's input parameters (e.g., a column `asin` for `amazon_pdp`, or `keyword` for `amazon_serp`).
Use **Download example CSV** in the Create Job panel to get a template pre-filled with the correct columns for the selected template.
Paste a JSON array of input objects directly. Each object's keys must match the template's input parameters. Best for quick tests or smaller, ad-hoc input sets.
```json theme={"system"}
[
{ "asin": "B08N5WRWNW" },
{ "asin": "B0DLKFK6LR" },
{ "asin": "B09XS7JWHH" }
]
```
Point the job at a CSV, JSON, or Parquet file in your S3 bucket so the input set is read at run time. The path can also point to a folder - the latest file under that prefix (by `LastModified`) is used.
Supported extensions: `.csv`, `.json`, `.parquet`.
Grant Nimble access to the bucket via a bucket policy. See the [Amazon S3 connection](/nimble-sdk/agentic/jobs-connections/amazon-s3) for the policy template and how to apply it. Use **Test Connection** in the Job form to verify the policy.
Read the input set directly from a table in your **Databricks Unity Catalog** via Delta Sharing. Each Job run reads the latest contents of the shared table.
Fields: `Provider`, `Share`, `Schema`, `Table`. Set them after publishing a Delta Sharing share whose recipient is bound to Nimble's metastore (`aws:us-east-1:2f3ba1b1-429c-491f-b37d-ebb46f22f3e9`).
See the [Databricks connection → Input](/nimble-sdk/agentic/jobs-connections/databricks#input-share-your-table-with-nimble) for the full `databricks` CLI sequence and every property.
Describe the input set in natural language and have Nimble generate it for you (e.g., *"the top 500 best-selling kitchen appliances on Amazon"*). Coming soon.
Input rows can also include the [shared params](/nimble-sdk/web-tools/extract/template#parameters) accepted by every template — `country`, `locale`, `tag`, `parse` — to control localization or tag rows for downstream filtering.
#### Destination
Where Nimble delivers the assembled results when each run completes.
Results are stored on Nimble and downloadable from the run page. Each completed run produces a single file containing every successful template result for that run.
Push results directly to your own S3 bucket as soon as a run completes. Choose any output format (JSON, CSV, or Parquet).
Grant Nimble write access via the same bucket policy used for inputs. See [S3 bucket permissions](/nimble-sdk/agentic/jobs-connections/amazon-s3). Use **Test Connection** in the Job form to verify the policy before saving the job.
Deliver results straight into your **Databricks Unity Catalog** as a Delta Sharing share. Each Job run refreshes the shared table; you mount it once as a UC catalog and query it like any other UC table.
Fields: `Share name`, `Recipient name`, `Metastore ID` (your UC global metastore ID, in `::` form), `Shared as` (the `.
` alias visible on your side).
**Retention: 14 days** from each successful run - beyond that window, the shared table is not guaranteed to remain available. To keep the data longer, materialize it into a UC table you own (see [Persisting the data](/nimble-sdk/agentic/jobs-connections/databricks#persisting-the-data)).
See the [Databricks connection → Destination](/nimble-sdk/agentic/jobs-connections/databricks#destination-deliver-job-output-to-your-unity-catalog) for how to find your metastore ID and the `databricks` CLI sequence to mount the share.
Output format for the delivered file:
* `JSON` — one JSON document per run, with all rows under a `files` array. Best for nested or unstructured template outputs.
* `CSV` — flat tabular output, one row per template invocation. Best for spreadsheets and BI tools.
* `Parquet` — columnar binary format. Best for analytical workloads, data lakes, and any pipeline where you'd otherwise convert CSV to Parquet later.
When enabled, each output row also includes the original input fields (e.g., the `asin` you submitted) alongside the parsed template result. Makes it trivial to join results back to the input set without relying on row order.
#### Run On
Controls **when** the job executes.
The job only runs when you click **Run** on the job page (or call the API). Use this for on-demand workloads where there's no fixed cadence — ad-hoc backfills, one-off competitor sweeps, or runs gated on an upstream event.
Run the job automatically on a recurring cadence. Five preset modes are supported:
| Cadence | Description |
| - | - |
| `Day` | Once every 24 hours, anchored to the job's creation time. |
| `Week` | Once every 7 days, anchored to the job's creation time. |
| `Month` | Once every calendar month, anchored to the job's creation date. |
| `Day at specific time` | Once a day at a chosen time of day (HH:MM, UTC). |
| `Custom` | A standard 5-field cron expression (UTC). e.g. `0 0 * * *` for daily at midnight UTC, `0 */6 * * *` for every 6 hours, `0 9 * * 1` for every Monday at 09:00. |
Manually triggered runs are still allowed on scheduled jobs — pressing **Run** kicks off an extra run without affecting the schedule.
### The Jobs list
The [Jobs page](https://online.nimbleway.com/jobs) shows every job in your workspace with its template, last run timestamp, and trigger type (Manual or scheduled). Use the search box to filter by job name; the list paginates server-side, so it stays responsive even with thousands of jobs.
Click any job in the list to open its detail page.
### Monitoring runs
Every job has its own detail page with two tabs.
#### Runs tab
A reverse-chronological list of every execution of the job. Each row shows:
| Column | Description |
| - | - |
| `Run ID` | Unique identifier for the run (e.g., `run_1204402`). Click to open the run detail page. |
| `Status` | `Success`, `In progress`, or `Error`. |
| `Start Time` / `End Time` | Wall-clock start and end of the run. |
| `Success Rate` | Percentage of input rows that produced a successful template result. |
| `Actions` | Direct download of the run's result file. |
#### Monitoring tab
Aggregate health and volume of the job over time:
* **Total Runs** — number of runs executed since the job was created.
* **Availability** — percentage of scheduled runs that completed (didn't fail to start).
* **Success Rate** — percentage of input rows that produced a successful template result, averaged across runs.
* **Completeness** — percentage of expected output fields that were populated, averaged across rows. A useful proxy for "is the template still healthy on this site".
* **Total Rows** — cumulative number of template invocations executed across all runs.
* **Volume Overview** — bar chart of rows per run over the recent history.
Use **Completeness** as your early-warning signal. Success rate can stay at 100% even when a target site quietly stops returning a particular field — completeness will drop first.
#### Run detail
Clicking a Run ID opens a single run's detail page, which includes:
* **Status, # Inputs, # Results, Start Time, End Time, Job, Template** — quick metadata in the side panel.
* **Results Sample** — preview of the parsed output, plus a direct download link for the full result file.
* **Inputs Sample** — the exact input set the run executed against (useful for reproducing or debugging a specific run).
* **Errors Sample** — preview of any rows that failed, with their error reasons. Used to diagnose problems without scrolling through the full result file.
The format toggle at the top of each sample (JSON / CSV / Parquet) lets you preview the same data in any of the three supported formats.
### Header actions
From any job's detail page, three header actions control the job:
* **Run** — manually trigger a run right now. Works on both manual and scheduled jobs.
* **Edit** — open the job configuration in the same panel used to create it. Editing does not affect runs already in progress; the next run picks up the new configuration.
* **Delete** — permanently remove the job and its run history. Cannot be undone.
## Use cases
Run `amazon_pdp` or `walmart_pdp` every night across your full SKU catalog. Use Parquet output and an [S3 destination](/nimble-sdk/agentic/jobs-connections/amazon-s3) to land directly in your data lake.
Run `google_search` hourly across your priority keywords. Track rank movement over time using the run-by-run history.
Run `chatgpt`, `perplexity`, or `gemini` daily on prompts that mention your brand or category. Watch how AI platforms describe you over time.
Run a custom template weekly across every competitor product page. Use **Completeness** to catch the day a competitor changes their page structure.
## Jobs vs other tools
| What you need | Use |
| - | - |
| Run a template **once**, on a single input | [Template.run](/nimble-sdk/web-tools/extract/template#quick-start) |
| Run a template on **a few thousand inputs**, one-time | [Template batch](/nimble-sdk/web-tools/extract/template#batch) |
| Run a template on **any size input set**, on a recurring schedule | **Jobs** |
| Get notified or have results land in cloud storage automatically | Jobs with [S3 destination](/nimble-sdk/agentic/jobs-connections/amazon-s3) or [Async + callback](/nimble-sdk/admin/callbacks-and-delivery) |
Jobs are designed for **recurring, large-scale, hands-off** work. If you're calling a template ad-hoc from your own code, the synchronous and batch APIs stay the right tool.
## Next steps
Create your first job in the Nimble Platform
Full endpoint and schema reference for driving jobs from code
Browse pre-built templates to power your jobs
# Job Connections
Source: https://docs.nimbleway.com/nimble-sdk/agentic/jobs-connections
Connect a Job's input source and destination to your own storage
A [Job](/nimble-sdk/agentic/jobs) can read its input set from your own storage and write its assembled results back to it. Each direction - **input source** and **destination** - is configured independently in the Job form's **Inputs** and **Destination** strips, and each can use a different connector.
This page covers storage connections for **Jobs**. For receiving results from async API requests (`/extract/async`, `/extract/batch`, `/extract/templates/async`, `/crawl`), see [Callbacks & Delivery](/nimble-sdk/admin/callbacks-and-delivery).
## Available connectors
Read inputs from and write outputs to an S3 bucket, via a single bucket policy.
Read from and deliver to your Unity Catalog via Delta Sharing.
| Connector | Input source | Destination |
| - | - | - |
| [Amazon S3](/nimble-sdk/agentic/jobs-connections/amazon-s3) | ✓ | ✓ |
| [Databricks](/nimble-sdk/agentic/jobs-connections/databricks) | ✓ | ✓ |
## Testing a connection
Every connector is verified the same way: enter the connection details in the Job form and click **Test Connection**. A green chip confirms the credentials and permissions are correct before you save the job. Each connector page documents exactly what its test probes and how to read the result.
# Amazon S3
Source: https://docs.nimbleway.com/nimble-sdk/agentic/jobs-connections/amazon-s3
Connect a Job's input source and destination to your S3 bucket
A [Job](/nimble-sdk/agentic/jobs) can read its input set from an S3 bucket and write its assembled results back to one. Both directions are wired through a single bucket policy that grants Nimble's IAM principal access to specific prefixes. Nimble does not assume a role in the customer account.
Use **Test Connection** in the Job form to verify the policy before saving the job.
## Nimble's IAM principal
Every Job runs under a single IAM user:
```text theme={"system"}
arn:aws:iam::744254827463:user/crawlit-scrapy
```
Use this ARN as the `Principal` in the bucket policy.
## Required permissions
| Prefix | Permission |
| - | - |
| `s3://YOUR_BUCKET` (whole bucket) | `s3:ListBucket` |
| `s3://YOUR_BUCKET/input/*` | `s3:GetObject` |
| `s3://YOUR_BUCKET/output/*` | `s3:GetObject`, `s3:PutObject`, `s3:AbortMultipartUpload`, `s3:ListMultipartUploadParts` |
`s3:AbortMultipartUpload` and `s3:ListMultipartUploadParts` are required because large output files are written in parts. If an upload fails mid-way, Nimble aborts the incomplete multipart upload so partial bytes do not accumulate in the bucket.
`s3:DeleteObject` is not requested. Nimble never deletes files in the bucket - including the connection-test probe described below.
## Bucket policy template
Replace `YOUR_BUCKET` with the bucket name. Adjust the `input/` and `output/` prefixes to match the paths used in the Job form.
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "NimbleListBucket",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::744254827463:user/crawlit-scrapy" },
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR_BUCKET"
},
{
"Sid": "NimbleReadInputPrefix",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::744254827463:user/crawlit-scrapy" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET/input/*"
},
{
"Sid": "NimbleReadWriteOutputPrefix",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::744254827463:user/crawlit-scrapy" },
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET/output/*"
}
]
}
```
## Applying the policy
In the AWS Console, open **S3** and click the bucket Nimble should access.
Open the **Permissions** tab. Click **Edit** under **Bucket policy**.
Paste the JSON above. Replace `YOUR_BUCKET` and the prefixes. Save changes.
Open the Job form. Paste the `s3://...` path and click **Test Connection**. A green chip confirms the policy is correct.
Keep **Block Public Access** enabled. The policy grants access only to Nimble's IAM user, not to the public.
## Test Connection - what it does
| Mode | Operation | Verifies |
| - | - | - |
| Read (input path) | `head_bucket` + list one object under the prefix | `s3:ListBucket` + `s3:GetObject` |
| Write (output path) | `head_bucket` + `put_object` of an empty `.nimble-connection-test` file at the prefix | `s3:ListBucket` + `s3:PutObject` |
The write test leaves an empty `.nimble-connection-test` file under the output prefix. The file is **not deleted** - the policy does not grant `s3:DeleteObject`, so cleanup is not possible. The key is deterministic, so repeated tests overwrite the same object. At most one residue file per output prefix. Remove it manually at any time.
## Interpreting the result
| Chip | Meaning | Fix |
| - | - | - |
| ✓ Connection OK | Permissions correct. The prefix has files. | Ready to use. |
| ✓ Connection OK - prefix is empty | Permissions correct. The prefix has no files yet. | Upload the first input file. |
| ✗ Access denied | The bucket policy is missing a required permission. | Re-apply the template above. |
| ✗ Bucket does not exist | The bucket name in the path is wrong. | Verify the bucket name. |
| ✗ Connection failed | Network, throttling, or unexpected S3 error. | Retry. Contact support if it persists. |
## Related
Browse every Job storage connector.
Connect a Job via Delta Sharing instead.
# Databricks
Source: https://docs.nimbleway.com/nimble-sdk/agentic/jobs-connections/databricks
Connect a Job's input source and destination via Delta Sharing
Select **Databricks** in the Inputs and/or Destination strip of the Job form. Both directions use Databricks-to-Databricks (D2D) Delta Sharing and require your workspace to be on **Databricks Unity Catalog**.
Databricks Delta Sharing lets a [Job](/nimble-sdk/agentic/jobs) read its input set directly from your Unity Catalog and/or deliver its assembled output back into your Unity Catalog. Each direction is configured independently and uses opposite Delta Sharing roles:
| Direction | Your role | Nimble's role |
| - | - | - |
| **Input** - share your table with Nimble | Provider | Recipient |
| **Destination** - mount Nimble's output | Recipient | Provider |
## Nimble's global metastore ID
Both directions reference Nimble's Unity Catalog metastore. Wherever the property tables below mention it, use:
```text theme={"system"}
aws:us-east-1:2f3ba1b1-429c-491f-b37d-ebb46f22f3e9
```
This is a **global** metastore ID in the `::` form required by Databricks D2D sharing. The bare UUID is not enough.
## Prerequisites
* Databricks CLI **v0.205+** authenticated against the workspace you want to share from / into. See the [Databricks CLI install guide](https://docs.databricks.com/aws/en/dev-tools/cli/install) and `databricks configure`.
* A metastore admin or a user with the `CREATE RECIPIENT`, `CREATE SHARE`, and `CREATE CATALOG` privileges on your UC metastore.
***
## Input: share your table with Nimble
In this direction, **you are the provider**: you publish a Delta Sharing share whose recipient is Nimble's metastore. On each Job run, the Job reads the current contents of the shared table.
### Job form fields
Set these in the Job form under **Inputs → Databricks**:
| Field | Description |
| - | - |
| `Provider` | Delta Sharing provider name **as it appears in Nimble's metastore** - Databricks D2D derives this on Nimble's receiving side from your metastore identity. Your Nimble account contact can confirm the exact string if you're unsure. |
| `Share` | Share name on your side (the value you pass to `databricks shares create --name <...>`). |
| `Schema` | Schema (database) inside the share that holds the table you want Nimble to read. |
| `Table` | Table name inside that schema. |
### One-time setup
The recipient is the Databricks-side object that grants Nimble permission to receive your shares.
```bash theme={"system"}
databricks recipients create --json '{
"name": "nimble",
"authentication_type": "DATABRICKS",
"data_recipient_global_metastore_id": "aws:us-east-1:2f3ba1b1-429c-491f-b37d-ebb46f22f3e9",
"comment": "Nimble Delta Sharing recipient"
}'
```
You only do this once - re-use the same recipient for every additional table you share with Nimble.
Pick any name you like (e.g., `nimble_inbox`). The Job form's `Share` field will use this value.
```bash theme={"system"}
databricks shares create \
--name nimble_inbox \
--comment "Tables shared with Nimble"
```
Replace `main.inbox.products` with the fully-qualified UC name of the source table (`..
`). The `shared_as` value sets how the table appears on Nimble's side - it **must match** the `Schema` and `Table` values in the Job form.
```bash theme={"system"}
databricks shares update nimble_inbox --json '{
"updates": [{
"action": "ADD",
"data_object": {
"name": "main.inbox.products",
"data_object_type": "TABLE",
"shared_as": "inbox.products"
}
}]
}'
```
For partitioned tables or history sharing options, see the full [`SharedDataObject` reference](https://docs.databricks.com/api/workspace/shares/update).
```bash theme={"system"}
databricks grants update share nimble_inbox --json '{
"changes": [{
"principal": "nimble",
"add": ["SELECT"]
}]
}'
```
Once this grant lands, the share becomes visible on Nimble's metastore. No support ticket, no manual coordination - the next Job run picks up the share **automatically**.
Every Job run reads the **latest** contents of the shared table; you do not need to re-publish the share unless you change the table name or schema.
### Adding more tables later
Only **steps 3 and 4** need to repeat for each additional table. The recipient (step 1) and the share (step 2) are reused across tables - just `ADD` a new `data_object` and re-grant `SELECT` if you create a new share instead of extending the existing one.
***
## Destination: deliver Job output to your Unity Catalog
In this direction, **Nimble is the provider** and **you are the recipient**. On the first Job run, Nimble makes the share available to your metastore (creating both the share and the recipient on demand, bound to your `Metastore ID`). You mount the share locally as a Unity Catalog catalog and query it like any other UC table.
**Data retention: 14 days.** Nimble guarantees the shared table stays queryable for **at least two weeks** after each successful Job run, but **not longer**. Beyond that window, the shared table is not guaranteed to remain available. If you need the data to persist - or want a frozen snapshot of every run - materialize it into a UC table you own. See [Persisting the data](#persisting-the-data).
### Job form fields
Set these in the Job form under **Destination → Databricks**:
| Field | Description |
| - | - |
| `Share name` | Share name **on Nimble's side** that the output table will be published to. Created on demand if it doesn't already exist. Pick any value - it's only visible to your account. |
| `Recipient name` | Recipient name **on Nimble's side** that represents your workspace. Created on demand and bound to `Metastore ID`. Use one recipient per consuming metastore. |
| `Metastore ID` | **Your** UC global metastore ID, in the form `::` (e.g. `aws:us-east-1:a9e65c72-c515-45bc-a600-3048a46bd6a8`). See "Finding your metastore ID" below. |
| `Shared as` | `.
` alias for the shared table - the **only** name visible on your side. Pick a clean, stable value; renaming it later invalidates the catalog mount on your side. |
### Finding your metastore ID
Run **one** of the following inside the Databricks workspace that will consume the share:
```sql SQL (any UC-enabled cluster or SQL warehouse) theme={"system"}
SELECT current_metastore();
```
```bash CLI theme={"system"}
databricks metastores summary | jq -r .global_metastore_id
```
The output is the value to enter in the `Metastore ID` field. The format is always `::` - the bare UUID returned by older Databricks endpoints is **not** accepted.
### One-time setup (after the first Job run)
The share becomes available to your metastore after the Job's first successful run. After that first run completes, run the following on your Databricks workspace:
Once Nimble has bound the recipient to your metastore, the share becomes visible in your UC metastore as a **provider**. List providers to find the exact name Databricks assigned it on your side:
```bash theme={"system"}
databricks providers list --output json \
| jq '.providers[] | select(.authentication_type=="DATABRICKS") | {name, recipient_profile_str}'
```
You can also do this in the UI: **Catalog → Delta Sharing → Shared with me → Providers**.
Replace `` with the name from the previous step. Confirm the share you configured in the Job form is listed and contains a table named after your `Shared as` value.
```bash theme={"system"}
databricks providers list-shares
```
Pick any local catalog name (e.g., `nimble`). Once mounted, the Job's output table is queryable as `.`.
```sql SQL theme={"system"}
CREATE CATALOG IF NOT EXISTS nimble
USING SHARE ``.``;
```
```bash CLI theme={"system"}
databricks catalogs create --json '{
"name": "nimble",
"provider_name": "",
"share_name": ""
}'
```
`` is the value entered in the Job form's `Share name` field. `` is the value from step 1.
The mount itself only grants access to the metastore admin who created it. Open it up to consumers with UC grants:
```bash theme={"system"}
databricks grants update catalog nimble --json '{
"changes": [{
"principal": "account users",
"add": ["USE_CATALOG", "USE_SCHEMA", "SELECT"]
}]
}'
```
Replace `account users` with whichever UC group / service principal should have read access.
The fully-qualified path is `.`. For `Shared as = data.products` and a catalog mounted as `nimble`:
```sql theme={"system"}
SELECT * FROM nimble.data.products LIMIT 100;
```
### Subsequent runs
Once mounted, every subsequent Job run refreshes the shared table - the catalog mount itself is permanent. Each successful run also **resets the 14-day retention clock** on the shared table; if a Job stops running (or runs less frequently than every two weeks), the table will eventually age out of the share. Re-run the `CREATE CATALOG` step only if you delete the catalog, rename `Shared as` on the Job, or Nimble changes the share name (which won't happen except on your explicit request).
Changing `Shared as` after the first run changes the **path** consumers query against (e.g., from `nimble.data.products` to `nimble.curated.products`). Plan downstream queries and views accordingly - and prefer keeping `Shared as` stable.
### Persisting the data
The shared table is only guaranteed to be queryable for **14 days** after each Job run. To hold the data longer - or to keep a frozen per-run snapshot for audit / historical analysis - materialize it into a UC table you own. Two common patterns:
```sql Overwrite (latest snapshot) theme={"system"}
CREATE OR REPLACE TABLE my_catalog.my_schema.products
AS SELECT * FROM nimble.data.products;
```
```sql Append (run history) theme={"system"}
INSERT INTO my_catalog.my_schema.products_history
SELECT *, current_timestamp() AS snapshot_at
FROM nimble.data.products;
```
Wire this as a Databricks Job, a [Lakeflow Declarative Pipeline](https://docs.databricks.com/aws/en/delta-live-tables), or any other UC-aware orchestrator, scheduled to run shortly after Nimble's Job cadence. Once materialized, the data is fully under your control and survives Nimble's retention window.
Schedule the materialization a few minutes **after** Nimble's Job is expected to finish, not at the same time. If you run too early you'll snapshot the previous run; if you run too late you stay within the 14-day window with plenty of buffer either way.
### Reference summary
| What | Value |
| - | - |
| Nimble's global metastore ID (used when **you** create a recipient pointing to Nimble) | `aws:us-east-1:2f3ba1b1-429c-491f-b37d-ebb46f22f3e9` |
| Input direction - your role | Provider (you run `databricks recipients create` + `shares create` + `shares update` + `grants update share`) |
| Destination direction - your role | Recipient (you run `databricks providers list` + `catalogs create` + `grants update catalog`) |
| Where to find your global metastore ID | `SELECT current_metastore()` or `databricks metastores summary` |
| Destination data retention | **14 days** from each successful Job run. Materialize into a UC table you own to retain longer. |
## Related
Browse every Job storage connector.
Connect a Job via an S3 bucket policy instead.
# Onboard your Agent
Source: https://docs.nimbleway.com/nimble-sdk/getting-started/overview
Web data APIs for AI: search, extract, crawl, and research the live web
Nimble gives your application and your AI agents live web data through a single API. Search the web, extract any page, crawl whole sites, or hand off entire research tasks. Every product returns structured, LLM-ready output.
Fastest path: paste this into your coding agent and it installs Nimble itself.
```text theme={"system"}
Read and follow https://docs.nimbleway.com/agents.md
```
## Install in your coding agent
Prefer to do it yourself? No API key needed where the client supports OAuth. Works in Claude, Cursor, Codex, and Cortex Code.
Installs the Nimble skills and configures the MCP server.
```bash theme={"system"}
claude plugin marketplace add Nimbleway/agent-skills && \
claude plugin install nimble@nimble-plugin-marketplace
```
Run `/mcp` and confirm `nimble` appears in your connected servers.
One click, from the Anthropic and Partners directory inside Claude.
Approve the scopes in your browser. There is no API key to paste or manage.
One click, then confirm the install in Cursor.
Then replace `NIMBLE_API_KEY` in **Settings → MCP Servers → nimble-mcp-server** with your [Nimble API key](https://online.nimbleway.com/settings/api-keys). The deeplink ships a placeholder, not a key.
Teaches the agent which Nimble product fits a given job.
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a cursor
```
Open Settings, then MCP, and confirm `nimble-mcp-server` is listed.
Open **Plugins** in the Codex left menu and search for `Nimble`.
Prefer to install from source? Click **Add → Add a marketplace** and enter `https://github.com/Nimbleway/agent-skills.git` instead.
Click **Install plugin**, then approve the scopes in your browser. This installs the Nimble skills and the hosted MCP connection together. There is no API key to paste or manage.
Bundled skills register on a new session. Start a new task, type `@nimble`, and select **Nimble**.
Full walkthrough with screenshots: [Plugin Installation](/integrations/agent-skills/plugin-installation).
Codex stores MCP configuration in `~/.codex/config.toml`, or a project-scoped `.codex/config.toml`. Add:
```toml theme={"system"}
[mcp_servers.nimble-mcp-server]
url = "https://mcp.nimbleway.com/mcp"
bearer_token_env_var = "NIMBLE_API_KEY"
```
Codex reads `NIMBLE_API_KEY` from your environment at runtime, so the key is never written to the config file.
Installs the Nimble skills into Codex's skill discovery paths (`~/.agents/skills/`).
```bash theme={"system"}
npx skills add Nimbleway/agent-skills -a codex
```
Run `/mcp` inside the Codex TUI to confirm the server, and `/skills` to confirm the skills. Restart Codex if they do not appear.
Adds the Nimble MCP server to Snowflake's terminal agent.
```bash theme={"system"}
cortex mcp add nimble https://mcp.nimbleway.com/mcp --transport http
```
No `Authorization` header is needed. The Nimble MCP server advertises OAuth and Cortex Code runs the flow on first connection, storing tokens in your OS keychain.
Run `cortex mcp list` to confirm, then `cortex mcp start`.
Once it is installed, ask for what you want:
```text theme={"system"}
Compare prices for the Sony WH-1000XM5 across Amazon, Walmart, and Best Buy
using Nimble. Show price, availability, and rating from each.
```
No coding agent? The [Quickstart](/nimble-sdk/getting-started/quickstart) has working code in Python, TypeScript, Go, cURL, and the CLI. Need MCP, a warehouse app, or another path? See [all install options](/integrations/overview).
## Build with Nimble
Pick a product, copy the setup prompt, and paste it into your coding agent. Each prompt tells the agent when to use that product, when to reach for a different one, and the gotchas per language.
Brief it like an analyst and it plans its own searches, reads the sources, and writes the answer. Every claim comes back with a citation and a confidence grade. [View docs →](/nimble-sdk/web-search-agents/overview)
````markdown theme={"system"}
# Nimble Agent: setup prompt
You are integrating **Nimble Agent** into this project.
## When to use it
Use a Nimble Agent when the task is a research question rather than a lookup.
It plans its own searches, reads sources, and returns a written answer with a
citation and a confidence grade per claim. It takes minutes, not seconds. For a
single fast lookup, use Search.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os, time
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
run = nimble.agents.run(
input="Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
)
agent_id = run.web_search_agent_id
while run.is_active:
time.sleep(10)
run = nimble.agents.runs.get(run.id, agent_id=agent_id)
result = nimble.agents.runs.result(run.id, agent_id=agent_id)
print(result.output.content)
print(result.output.trust.confidence)
```
## Notes
- Runs are asynchronous. Poll while `run.is_active` is true, then fetch the result.
- Keep `web_search_agent_id` from the first response. Later calls require it.
- `output.trust` grades every claim and links its sources. Surface it rather than
discarding it.
- Effort level trades depth against time and cost.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`. Follow-up calls take the id positionally and the rest as an
object: `nimble.agents.runs.get(run.id, { agent_id: agentId })`.
- Do not block on a single call. There is no synchronous variant.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/web-search-agents/overview
- REST: POST https://sdk.nimbleway.com/v2/agents/runs
- CLI: `nimble agents run --input "..."`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
Real-time results from across the web, ranked and structured for a model to consume. Choose how deep to go, from metadata only through to full page content. [View docs →](/nimble-sdk/web-tools/search)
````markdown theme={"system"}
# Nimble Search: setup prompt
You are integrating **Nimble Search** into this project.
## When to use it
Use Search when you need current web results for a query. It returns ranked
results with titles, URLs, and descriptions. If you need a written answer with
citations rather than a result list, use a Nimble Agent. If you already know
the page you want, use Extract.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.search(
query="best CRM tools for startups 2026",
max_results=5,
search_depth="standard", # lite | standard
)
for item in result.results:
print(item.title, item.url)
```
## Notes
- `search_depth` controls cost and latency. `standard` is the default and returns
rich content. `lite` returns title, URL, and description only.
- Full page extraction is a separate flag, `full_content=True`, not a depth. It
works alongside either `search_depth` value.
- `focus` narrows the result type, for example a shopping or news focus.
- Read `total_results`. Do not assume a fixed count.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`. `max_results` and `search_depth` keep their underscores.
- Go takes optional scalars through `param.NewOpt(...)` and enums as typed
constants such as `nimble.SearchParamsSearchDepthLite`.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/web-tools/search
- REST: POST https://sdk.nimbleway.com/v2/search
- CLI: `nimble search --query "..." --max-results 5`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
Maintained extractors for popular sites such as Amazon and Google Maps. Pass a template and its parameters and get clean structured fields, with no selectors to write. [View docs →](/nimble-sdk/web-tools/extract/template)
````markdown theme={"system"}
# Nimble Extract Template: setup prompt
You are integrating **Nimble Extract Template** into this project.
## When to use it
Use an Extract Template for sites that already have a maintained template, such as
Amazon product pages or Google Maps. Pass a template name and its parameters and
get structured fields back. Nimble maintains the template, so it keeps working when the site changes its markup. For an arbitrary URL with
no template, use Extract.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.extract.templates.run(
template="amazon_pdp",
params={"asin": "B0DKB1GWML"},
)
print(result.data.parsing)
```
## Notes
- List what exists with `nimble.extract.templates.list()` before assuming a
template is available.
- Each template declares its own required params. Read the template first.
- A template can be generated for a site that lacks one.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`.
- `params` is a free-form object. Its keys come from the template, not the SDK,
so they are not type-checked.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/template
- REST: POST https://sdk.nimbleway.com/v2/extract/templates/run
- CLI: `nimble extract:templates run --template amazon_pdp`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
Any URL to clean, LLM-ready content, with JavaScript rendering handled for you. Return markdown, raw HTML, parsed fields, or a screenshot. [View docs →](/nimble-sdk/web-tools/extract/quickstart)
````markdown theme={"system"}
# Nimble Extract: setup prompt
You are integrating **Nimble Extract** into this project.
## When to use it
Use Extract when you have a specific URL and want its content. It handles
JavaScript rendering and returns whichever formats you ask for. For a popular
site with a known layout, prefer an Extract Template: it returns structured
fields without selectors.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.extract.run(
url="https://www.nimbleway.com",
formats=["markdown"], # html | markdown | parsing | screenshots
)
print(result.data.markdown)
```
## Notes
- Pass `formats=["markdown"]` for LLM input. Add `"html"` only if you need raw markup.
- Heavy pages can exceed a sync request. Use the async variant and poll the task.
- Geo-targeting, stealth mode, and browser actions are per-request options.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`.
- The response nests content under `data`, so read `result.data.markdown`, not
`result.markdown`.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/quickstart
- REST: POST https://sdk.nimbleway.com/v2/extract
- CLI: `nimble extract run --url "..." --format markdown`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
Every URL on a domain, gathered from links and sitemaps without fetching page content. Use it to pick your targets before you extract. [View docs →](/nimble-sdk/web-tools/map)
````markdown theme={"system"}
# Nimble Map: setup prompt
You are integrating **Nimble Map** into this project.
## When to use it
Use Map to plan work before extracting. It returns the URLs on a domain by
following links and reading sitemaps, without fetching page content. Pick targets
from it, then pass those URLs to Extract. To get content from every page in one
call instead, use Crawl.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.map(
url="https://www.nimbleway.com",
sitemap="include",
)
for link in result.links:
print(link.url)
```
## Notes
- Map returns links and metadata, not page content, so it costs far less to run than Crawl.
- `sitemap="include"` merges sitemap entries with discovered links.
- Large sites return a lot of URLs. Filter before extracting.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`.
- Results are under `links`, each with `url`, `title`, and `description`.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/web-tools/map
- REST: POST https://sdk.nimbleway.com/v2/map
- CLI: `nimble map --url "..." --sitemap include`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
Bind an extractor to a schedule and a destination, and the data keeps arriving. Runs on cron and delivers files to S3 or your warehouse. [View docs →](/nimble-sdk/agentic/jobs)
````markdown theme={"system"}
# Nimble Extract Jobs: setup prompt
You are integrating **Nimble Extract Jobs** into this project.
## When to use it
Use an Extract Job when the same extraction has to run repeatedly. A job binds a
template to a set of inputs, a schedule, and a destination, then delivers files
there on every run. For a one-off extraction, call Extract or an Extract Template
directly instead.
## Setup
```bash
pip install nimble_python # Python
npm install @nimble-way/nimble-js # TypeScript
export NIMBLE_API_KEY="your-api-key"
```
## Example (Python)
```python
import os, time
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
job = nimble.jobs.create(
name="daily_amazon_top_skus",
extract_template_name="amazon_pdp",
schedule={"cron": "0 7 * * *", "enabled": True},
inputs={"type": "inline", "data": [{"asin": "B08N5WRWNW"}]},
destination={
"type": "s3",
"path": "s3://my-bucket/amazon-skus/",
"format": "parquet",
},
)
run = nimble.jobs.runs.create(job.id)
artifacts = nimble.jobs.runs.artifacts.list(run.id)
```
## Notes
- A job needs four things: a template, inputs, a schedule, and a destination.
- `schedule.cron` is standard cron. Set `enabled: false` to create it paused.
- Trigger a run immediately with `jobs.runs.create` rather than waiting for the cron.
- Output arrives as artifacts. List them, then request a download URL.
- Destinations include S3 and Databricks; the connection is configured separately.
## Language notes
- TypeScript import is a default export: `import Nimble from "@nimble-way/nimble-js"`.
- The TypeScript client keeps the API's underscore field names. Write `max_results`, not `maxResults`. One exception to watch: the artifact download helper is
`downloadURL` in TypeScript and `download_url` in Python.
- `inputs` and `destination` are free-form objects whose shape depends on the type
you pass, so they are not fully type-checked.
## Links
- Docs: https://docs.nimbleway.com/nimble-sdk/agentic/jobs
- REST: POST https://sdk.nimbleway.com/v2/jobs
- CLI: `nimble jobs create --name ... --extract-template-name ...`
## Other Nimble products
| Product | Returns | Reach for it when |
|---------|---------|-------------------|
| **Agent** | Research question to a cited answer plus a trust report | The task needs multi-step research, not a single lookup |
| **Search** | Query to ranked results with titles, URLs, descriptions | You need current facts, or candidate URLs to feed into Extract |
| **Extract Template** | Template name and params to structured fields | The site has a maintained template, so no selectors are needed |
| **Extract** | URL to clean markdown, HTML, or parsed fields | You have a specific page and want its content |
| **Map** | Domain to every URL, no page content | Planning which pages to fetch before extracting |
| **Crawl** | Domain to content from many pages, async | You want a whole section of a site in one request |
| **Extract Jobs** | Template plus schedule plus destination to delivered files | The same extraction has to run repeatedly, on a schedule |
````
## Next steps
Your first API call in under a minute
Plugin, MCP, SDK, or warehouse: which path fits and how each authenticates
Hand off a research question, get a cited answer back
Configure and run agents in the browser, no code
# Quickstart
Source: https://docs.nimbleway.com/nimble-sdk/getting-started/quickstart
Get a key, install a client, and call every Nimble product
Every Nimble product in Python, TypeScript, Go, cURL, and the CLI. Copy, paste, and run.
Prefer not to write code? Install the [Nimble plugin](/integrations/agent-skills/plugin-installation) and describe what you need in plain language. [Onboard your Agent](/nimble-sdk/getting-started/overview) has the one-command setup for every coding agent.
[Sign up](https://online.nimbleway.com/signup) and copy your key from [Settings → API Keys](https://online.nimbleway.com/settings/api-keys). Then set it as an environment variable:
```bash macOS / Linux theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
```bash Windows (PowerShell) theme={"system"}
$env:NIMBLE_API_KEY="your-api-key"
```
Every SDK and the CLI read `NIMBLE_API_KEY` automatically.
Requires Python 3.9+, Node.js 20 LTS+, or Go 1.22+.
```bash Python theme={"system"}
pip install nimble_python
```
```bash TypeScript theme={"system"}
npm install @nimble-way/nimble-js
```
```bash Go theme={"system"}
go get github.com/Nimbleway/nimble-go@latest
```
```bash CLI theme={"system"}
npm install -g @nimble-way/nimble-cli
```
Full client docs: [Python](/nimble-sdk/sdks/python), [Node](/nimble-sdk/sdks/node), [Go](/nimble-sdk/sdks/go), and [CLI](/nimble-sdk/sdks/cli). Python also offers an aiohttp extra: `pip install "nimble_python[aiohttp]"`.
Calling the API over plain HTTP? Skip this step and see the [API Reference](/api-reference/introduction).
Every example below reads the key from `NIMBLE_API_KEY`. The first Search example is a complete program; the rest are abbreviated and assume you have already created a client the same way.
## Search
Real-time web search with structured results:
```python Python theme={"system"}
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.search(
query="latest developments in AI agents",
max_results=5
)
for r in result.results:
print(f"- {r.title}: {r.url}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.search({
query: "latest developments in AI agents",
max_results: 5,
});
result.results.forEach((r) => {
console.log(`- ${r.title}: ${r.url}`);
});
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
"os"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
"github.com/Nimbleway/nimble-go/packages/param"
)
func main() {
client := nimble.NewClient(option.WithAPIKey(os.Getenv("NIMBLE_API_KEY")))
result, err := client.Search(context.Background(), nimble.SearchParams{
Query: "latest developments in AI agents",
MaxResults: param.NewOpt(int64(5)),
})
if err != nil {
panic(err)
}
for _, r := range result.Results {
fmt.Printf("- %s: %s\n", r.Title, r.URL)
}
}
```
```bash CLI theme={"system"}
nimble search \
--query "latest developments in AI agents" \
--max-results 5
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/search' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"query": "latest developments in AI agents",
"max_results": 5
}'
```
## Extract
Get clean HTML and structured data from any URL:
```python Python theme={"system"}
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.extract.run(
url="https://www.example.com",
render=True,
formats=["html", "markdown"]
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
formats: ["html", "markdown"],
});
console.log(result.data?.html);
```
```go Go theme={"system"}
result, err := client.Extract.Run(ctx, nimble.ExtractRunParams{
URL: "https://www.example.com",
Render: nimble.ExtractRunParamsRenderUnion{OfBool: param.NewOpt(true)},
Formats: []string{"html", "markdown"},
})
if err != nil {
panic(err)
}
fmt.Println(result.Data.HTML)
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--render true \
--format html --format markdown
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"formats": ["html", "markdown"]
}'
```
## Extract Template
Run pre-built Extract Templates for popular sites, or [create one for any website](https://online.nimbleway.com/workflow-builder):
```python Python theme={"system"}
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.extract.templates.run(
template="amazon_pdp",
params={
"asin": "B08N5WRWNW"
}
)
parsed = result.data.parsing
print(f"Product: {parsed['product_title']}")
print(f"Price: ${parsed['web_price']}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.extract.templates.run({
template: "amazon_pdp",
params: {
asin: "B08N5WRWNW",
},
});
const parsed = result.data?.parsing as Record;
console.log(`Product: ${parsed.product_title}`);
console.log(`Price: $${parsed.web_price}`);
```
```go Go theme={"system"}
result, err := client.Extract.Templates.Run(ctx, nimble.ExtractTemplateRunParams{
Template: "amazon_pdp",
Params: map[string]any{
"asin": "B08N5WRWNW",
},
})
if err != nil {
panic(err)
}
fmt.Println(result.Data.Parsing)
```
```bash CLI theme={"system"}
nimble extract:templates run \
--template amazon_pdp \
--params '{asin: B08N5WRWNW}'
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/templates/run' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"template": "amazon_pdp",
"params": {
"asin": "B08N5WRWNW"
}
}'
```
## Map
Fast URL discovery and site structure mapping:
```python Python theme={"system"}
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.map(
url="https://www.example.com",
sitemap="include"
)
for link in result.links:
print(f"{link.title}: {link.url}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.map({
url: "https://www.example.com",
sitemap: "include",
});
result.links.forEach((link) => {
console.log(`${link.title}: ${link.url}`);
});
```
```go Go theme={"system"}
result, err := client.Map(ctx, nimble.MapParams{
URL: "https://www.example.com",
Sitemap: nimble.MapParamsSitemapInclude,
})
if err != nil {
panic(err)
}
for _, link := range result.Links {
fmt.Printf("%s: %s\n", link.Title, link.URL)
}
```
```bash CLI theme={"system"}
nimble map \
--url "https://www.example.com" \
--sitemap include
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/map' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"sitemap": "include"
}'
```
## Crawl
Extract content from entire websites:
```python Python theme={"system"}
import os
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
result = nimble.crawl.run(
url="https://docs.example.com",
limit=10
)
print(f"Crawl started: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.crawl.run({
url: "https://docs.example.com",
limit: 10,
});
console.log(`Crawl started: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```go Go theme={"system"}
result, err := client.Crawl.Run(ctx, nimble.CrawlRunParams{
URL: "https://docs.example.com",
Limit: param.NewOpt(int64(10)),
})
if err != nil {
panic(err)
}
fmt.Println("Crawl started:", result.CrawlID)
fmt.Println("Status:", result.Status)
```
```bash CLI theme={"system"}
nimble crawl run \
--url "https://docs.example.com" \
--limit 10
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://docs.example.com",
"limit": 10
}'
```
## Agent
Ask a research question, get a cited answer. See the full flow in the [Agent quickstart](/nimble-sdk/web-search-agents/quickstart):
```python Python theme={"system"}
import os, time
from nimble_python import Nimble
nimble = Nimble(api_key=os.environ["NIMBLE_API_KEY"])
run = nimble.agents.run(
input="Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
)
agent_id = run.web_search_agent_id
while run.is_active:
time.sleep(10)
run = nimble.agents.runs.get(run.id, agent_id=agent_id)
result = nimble.agents.runs.result(run.id, agent_id=agent_id)
print(result.output.content)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
let run = await nimble.agents.run({
input: "Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
});
const agentId = run.web_search_agent_id;
while (run.is_active) {
await new Promise((r) => setTimeout(r, 10000));
run = await nimble.agents.runs.get(run.id, { agent_id: agentId });
}
const result = await nimble.agents.runs.result(run.id, { agent_id: agentId });
if ("output" in result) {
console.log(result.output.content);
}
```
```go Go theme={"system"}
run, err := client.Agents.Run(ctx, nimble.AgentRunParams{
Input: "Compare the pricing of Datadog, Grafana Cloud, and New Relic.",
})
if err != nil {
panic(err)
}
for run.IsActive {
time.Sleep(10 * time.Second)
polled, err := client.Agents.Runs.Get(ctx, run.ID, nimble.AgentRunGetParams{
AgentID: run.WebSearchAgentID,
})
if err != nil {
panic(err)
}
run.IsActive = polled.IsActive
}
result, err := client.Agents.Runs.Result(ctx, run.ID, nimble.AgentRunResultParams{
AgentID: run.WebSearchAgentID,
})
if err != nil {
panic(err)
}
fmt.Println(result.Output.Content.OfString)
```
```bash CLI theme={"system"}
RUN=$(nimble agents run \
--input "Compare the pricing of Datadog, Grafana Cloud, and New Relic.")
RUN_ID=$(echo "$RUN" | jq -r .id)
AGENT_ID=$(echo "$RUN" | jq -r .web_search_agent_id)
# repeat while "is_active" is true
nimble agents:runs get --agent-id "$AGENT_ID" --run-id "$RUN_ID"
# then fetch the result
nimble agents:runs result --agent-id "$AGENT_ID" --run-id "$RUN_ID"
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents/runs' \
--header "Authorization: Bearer $NIMBLE_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"input": "Compare the pricing of Datadog, Grafana Cloud, and New Relic."
}'
# => { "id": "task_run_...", "web_search_agent_id": "wsa_...", "status": "queued" }
# repeat while "is_active" is true
curl "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs/$RUN_ID" \
--header "Authorization: Bearer $NIMBLE_API_KEY"
# then fetch the result
curl "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs/$RUN_ID/result" \
--header "Authorization: Bearer $NIMBLE_API_KEY"
```
## Next steps
Plugin, MCP, SDK, or warehouse: which path fits and how each authenticates
Full REST documentation for every endpoint
Efforts, trust reports, and use cases
Concurrency and throughput per plan
# CLI
Source: https://docs.nimbleway.com/nimble-sdk/sdks/cli
Official Nimble CLI — interact with the Nimble API directly from your terminal
The Nimble CLI lets you run extractions, searches, crawls, and agents directly from your terminal — no code required.
## Installation
Install globally with npm (Node.js 20+):
```bash theme={"system"}
npm install -g @nimble-way/nimble-cli
```
Verify the installation:
```bash theme={"system"}
nimble --version
```
## Authentication
Set your API key as an environment variable:
```bash theme={"system"}
export NIMBLE_API_KEY="your-api-key"
```
Get your API key from [Settings → API Keys](https://online.nimbleway.com/settings/api-keys).
## Command Structure
All commands follow a resource-based pattern:
```bash theme={"system"}
nimble [resource] [flags...]
```
Run `nimble --help` to list all resources, or `nimble [resource] --help` for resource-specific usage.
## Core Commands
### Extract
Extract clean content from any URL:
```bash theme={"system"}
nimble extract run --url "https://www.example.com"
```
With JavaScript rendering and geo-targeting:
```bash theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--render true \
--country US
```
### Search
Perform a real-time web search:
```bash theme={"system"}
nimble search --query "best web scraping tools" --country US
```
### Map
Discover all URLs in a domain:
```bash theme={"system"}
nimble map --url "https://www.example.com" --sitemap only
```
### Crawl
Crawl an entire website:
```bash theme={"system"}
nimble crawl run --url "https://www.example.com" --limit 100
```
### Extract Template
Run a pre-built Extract Template:
```bash theme={"system"}
nimble extract:templates run --template amazon_pdp --params '{asin: B08N5WRWNW}'
```
### Agent
Ask a research question — an agent is generated for you:
```bash theme={"system"}
nimble agents run \
--input "Compare the pricing of Datadog, Grafana Cloud, and New Relic."
```
## Output Formats
Control response formatting with the `--format` flag:
| Format | Description |
| - | - |
| `auto` | Automatic (default) |
| `pretty` | Pretty-printed JSON |
| `json` | Compact JSON |
| `jsonl` | JSON Lines (one object per line) |
| `yaml` | YAML output |
| `raw` | Raw API response body |
| `explore` | Interactive explorer view |
```bash theme={"system"}
nimble extract run --url "https://www.example.com" --format pretty
```
## Transforming Output
Use `--transform` with [GJSON syntax](https://github.com/tidwall/gjson) to extract specific fields:
```bash theme={"system"}
# Extract just the task_id
nimble extract run --url "https://www.example.com" --transform "task_id"
# Extract a nested field
nimble extract run --url "https://www.example.com" --transform "data.html"
```
## File Arguments
Pass file contents as argument values using `@filename` syntax:
```bash theme={"system"}
# Pass a parser definition from a file
nimble extract run --url "https://www.example.com" --parse --parser @parser.json
```
Use `@file://filename` for plain text or `@data://filename` for base64-encoded content.
## Global Flags
| Flag | Description |
| - | - |
| `--help` | Show help for any command |
| `--debug` | Enable debug logging with full HTTP details |
| `--version`, `-v` | Display the CLI version |
| `--base-url` | Override the API base URL |
| `--format` | Set output format |
| `--format-error` | Set error output format |
| `--transform` | Transform output using GJSON syntax |
| `--transform-error` | Transform error output using GJSON syntax |
## Debug Mode
Enable `--debug` to inspect the full HTTP request and response — useful for troubleshooting:
```bash theme={"system"}
nimble extract run --url "https://www.example.com" --debug
```
## Next Steps
Full REST API documentation for all endpoints
Extract features and parameters
Real-time web search with structured results
Pre-built Extract Templates for popular platforms
# Go
Source: https://docs.nimbleway.com/nimble-sdk/sdks/go
Official Nimble SDK for Go — type-safe client for web extraction, search, crawl, map, and agents
Install the Nimble Go library to interact with the Nimble API from your Go applications. Built with idiomatic Go patterns, full context support, and automatic retries.
## Installation
```sh theme={"system"}
go get github.com/Nimbleway/nimble-go@latest
```
Requires **Go 1.22+**. Set your API key via the `NIMBLE_API_KEY` environment
variable or pass it directly using `option.WithAPIKey()`.
## Setup
Import the package and initialize the client:
```go Go theme={"system"}
package main
import (
"fmt"
"os"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(
option.WithAPIKey(os.Getenv("NIMBLE_API_KEY")),
)
_ = client
fmt.Println("Client ready")
}
```
## Quick Start
Extract content from a URL:
```go Go theme={"system"}
package main
import (
"context"
"fmt"
"os"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(
option.WithAPIKey(os.Getenv("NIMBLE_API_KEY")),
)
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
})
if err != nil {
panic(err)
}
fmt.Printf("Task ID: %s\n", result.TaskID)
}
```
## Core Methods
### Extract
Get clean HTML, markdown, or structured data from any URL:
```go Go theme={"system"}
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
Render: nimble.ExtractRunParamsRenderUnion{OfBool: nimble.Bool(true)},
})
if err != nil {
panic(err)
}
fmt.Println(result.TaskID)
```
Optional fields are wrapped in `param.Opt[T]`. Use the provided constructors —
`nimble.Bool()`, `nimble.String()`, `nimble.Int()` — to set them.
### Search
Perform real-time web searches:
```go Go theme={"system"}
result, err := client.Search(context.TODO(), nimble.SearchParams{
Query: "best web scraping tools",
Country: nimble.String("US"),
SearchDepth: nimble.SearchParamsSearchDepthStandard,
FullContent: param.NewOpt(true),
})
if err != nil {
panic(err)
}
fmt.Println(result)
```
### Map
Discover all URLs within a domain or sitemap:
```go Go theme={"system"}
result, err := client.Map(context.TODO(), nimble.MapParams{
URL: "https://www.example.com",
Sitemap: nimble.MapParamsSitemapOnly,
})
if err != nil {
panic(err)
}
fmt.Println(result)
```
### Crawl
Recursively crawl and extract an entire website:
```go Go theme={"system"}
result, err := client.Crawl.Run(context.TODO(), nimble.CrawlRunParams{
URL: "https://www.example.com",
Limit: nimble.Int(100),
})
if err != nil {
panic(err)
}
fmt.Println(result)
```
### Extract Template
Run pre-built Extract Templates for structured data from popular platforms:
```go Go theme={"system"}
result, err := client.Extract.Templates.Run(context.TODO(), nimble.ExtractTemplateRunParams{
Template: "amazon_pdp",
Params: map[string]any{
"asin": "B08N5WRWNW",
},
})
if err != nil {
panic(err)
}
fmt.Println(result.Data.Parsing)
```
Browse the full template catalog with `client.Extract.Templates.List()`.
## Error Handling
Check `err != nil` for any failed request. Use `errors.As` to inspect typed API errors:
```go Go theme={"system"}
import (
"errors"
"fmt"
nimble "github.com/Nimbleway/nimble-go"
)
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
})
if err != nil {
var apiErr *nimble.Error
if errors.As(err, &apiErr) {
fmt.Printf("API error — status: %d: %s\n",
apiErr.StatusCode, apiErr.Error())
}
panic(err)
}
```
The `*nimble.Error` type exposes:
* `StatusCode int` — HTTP status code
* `Error() string` — Human-readable error description
* `Request` / `Response` — the underlying HTTP exchange
## Configuration
### Retries
The SDK retries automatically on connection errors, timeouts, and server failures (2 retries by default):
```go Go theme={"system"}
// Configure globally
client := nimble.NewClient(
option.WithAPIKey(os.Getenv("NIMBLE_API_KEY")),
option.WithMaxRetries(3),
)
// Disable retries for a specific request
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
}, option.WithMaxRetries(0))
```
### Timeouts
Use Go's standard `context` package for request timeouts:
```go Go theme={"system"}
import (
"context"
"time"
)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := client.Extract.Run(ctx, nimble.ExtractRunParams{
URL: "https://www.example.com",
})
```
Or set a per-request timeout directly:
```go Go theme={"system"}
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
}, option.WithRequestTimeout(30*time.Second))
```
## Advanced
### Raw Response Access
Capture the raw HTTP response alongside the parsed result:
```go Go theme={"system"}
import "net/http"
var rawResponse *http.Response
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
}, option.WithResponseInto(&rawResponse))
if err != nil {
panic(err)
}
fmt.Println(rawResponse.Header.Get("x-request-id"))
fmt.Println(result.TaskID)
```
### Custom Request Options
Attach additional headers or query parameters to any request:
```go Go theme={"system"}
result, err := client.Extract.Run(context.TODO(), nimble.ExtractRunParams{
URL: "https://www.example.com",
},
option.WithHeader("X-Custom-Header", "value"),
option.WithQuery("debug", "true"),
)
```
## Next Steps
Full REST API documentation for all endpoints
Rendering, formats, stealth mode, and more
Real-time web search with structured results
Pre-built Extract Templates for popular platforms
# Node
Source: https://docs.nimbleway.com/nimble-sdk/sdks/node
Official Nimble SDK for Node.js and TypeScript — async client for web extraction, search, crawl, map, and agents
Install the Nimble JavaScript/TypeScript library to interact with the Nimble API from Node.js, Deno, Bun, Cloudflare Workers, and modern browsers. All methods are async and return typed responses.
## Installation
```bash theme={"system"}
npm install @nimble-way/nimble-js
```
Requires **Node.js 20 LTS+** (also supports Deno v1.28+, Bun 1.0+, and
Cloudflare Workers). Never hardcode your API key — use environment variables.
## Setup
Initialize the client with your API key:
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({
apiKey: process.env.NIMBLE_API_KEY,
});
```
## Quick Start
Extract content from a URL in one call:
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
});
console.log(result.task_id);
```
## TypeScript Types
The SDK ships full TypeScript definitions for all request parameters and responses, with IDE autocompletion:
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const params: Nimble.ExtractRunParams = {
url: "https://www.example.com",
render: true,
};
const result: Nimble.ExtractRunResponse = await nimble.extract.run(params);
console.log(result.task_id);
```
## Core Methods
### Extract
Get clean HTML, markdown, or structured data from any URL. Supports JavaScript rendering, stealth mode, browser actions, and more.
```typescript TypeScript theme={"system"}
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
});
console.log(result.task_id);
```
See the [Extract docs](/nimble-sdk/web-tools/extract/quickstart) for the full
parameter list — geo-targeting, output formats, parsing schemas, and more.
### Search
Perform real-time web searches and get structured results:
```typescript TypeScript theme={"system"}
const result = await nimble.search({
query: "best web scraping tools",
country: "US",
search_depth: "standard",
full_content: true,
});
console.log(result);
```
### Map
Discover all URLs within a domain or sitemap:
```typescript TypeScript theme={"system"}
const result = await nimble.map({
URL: "https://www.example.com",
sitemap: "only",
});
console.log(result);
```
### Crawl
Recursively crawl and extract an entire website at scale:
```typescript TypeScript theme={"system"}
const result = await nimble.crawl.run({
url: "https://www.example.com",
limit: 100,
});
console.log(result);
```
### Extract Template
Run pre-built Extract Templates for structured data from popular platforms:
```typescript TypeScript theme={"system"}
const result = await nimble.extract.templates.run({
template: "amazon_pdp",
params: {
asin: "B08N5WRWNW",
},
});
console.log(result.data?.parsing);
```
Browse the full template catalog with `nimble.extract.templates.list()`.
## Error Handling
The SDK throws typed `APIError` subclasses for failed requests:
```typescript TypeScript theme={"system"}
import Nimble, { APIError } from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
try {
const result = await nimble.extract.run({ url: "https://www.example.com" });
} catch (error) {
if (error instanceof APIError) {
console.log(`Status: ${error.status}`);
console.log(`Message: ${error.message}`);
}
throw error;
}
```
| Error Class | HTTP Status | When it occurs |
| - | - | - |
| `BadRequestError` | 400 | Invalid request parameters |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `PermissionDeniedError` | 403 | Insufficient account permissions |
| `NotFoundError` | 404 | Resource not found |
| `RateLimitError` | 429 | Request rate exceeded |
| `InternalServerError` | 500+ | Server-side error |
## Configuration
### Timeouts & Retries
The SDK retries automatically on connection errors and server failures. Defaults: **2 retries**, **3-minute timeout**.
```typescript TypeScript theme={"system"}
// Configure globally
const nimble = new Nimble({
apiKey: process.env.NIMBLE_API_KEY,
maxRetries: 3,
timeout: 60 * 1000, // 60 seconds in ms
});
// Override per-request
const result = await nimble.extract.run(
{ url: "https://www.example.com" },
{ maxRetries: 0 },
);
```
### Logging
Control log verbosity via environment variable:
```bash theme={"system"}
export NIMBLE_LOG=debug
```
Or configure programmatically using any compatible logger (pino, winston, consola, etc.):
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
import pino from "pino";
const nimble = new Nimble({
apiKey: process.env.NIMBLE_API_KEY,
logger: pino(),
logLevel: "debug",
});
```
## Advanced
### Raw Response Access
Access HTTP headers and status alongside the parsed response:
```typescript TypeScript theme={"system"}
// Get just the raw Response object
const raw = await nimble
.extract({ url: "https://www.example.com" })
.asResponse();
console.log(raw.headers.get("x-request-id"));
// Get both parsed result and raw response
const { data, response } = await nimble
.extract({ url: "https://www.example.com" })
.withResponse();
console.log(response.headers.get("x-request-id"));
console.log(data.task_id);
```
### Custom Fetch / Proxy
Provide a custom fetch implementation or configure proxies for Node.js, Bun, and Deno:
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
import { ProxyAgent } from "undici";
const nimble = new Nimble({
apiKey: process.env.NIMBLE_API_KEY,
fetchOptions: {
dispatcher: new ProxyAgent("http://proxy.example.com:8080"),
},
});
```
### MCP Server
The SDK includes a built-in [MCP Server](https://modelcontextprotocol.io) for AI assistant integrations, enabling API exploration and request testing directly from tools like Claude.
## Next Steps
Full REST API documentation for all endpoints
Rendering, formats, stealth mode, and more
Real-time web search with structured results
Pre-built Extract Templates for popular platforms
# Python
Source: https://docs.nimbleway.com/nimble-sdk/sdks/python
Official Nimble Python SDK — sync and async client for web extraction, search, crawl, map, and agents
Install the Nimble Python library to interact with the Nimble API from your Python applications. Supports both synchronous and asynchronous usage with full type hints.
## Installation
```bash pip theme={"system"}
pip install nimble_python
```
```bash pip (async HTTP backend) theme={"system"}
pip install "nimble_python[aiohttp]"
```
Requires **Python 3.9+**. Never hardcode your API key — use environment
variables instead.
## Setup
Initialize the client with your API key. We recommend loading it from the environment:
```python Python theme={"system"}
import os
from nimble_python import Nimble
client = Nimble(api_key=os.environ.get("NIMBLE_API_KEY"))
```
## Quick Start
Extract clean content from a URL in one call:
```python Python theme={"system"}
import os
from nimble_python import Nimble
client = Nimble(api_key=os.environ.get("NIMBLE_API_KEY"))
result = client.extract.run(url="https://www.example.com", render=True)
print(result.task_id)
```
## Core Methods
### Extract
Get clean HTML, markdown, or structured data from any URL. Supports JavaScript rendering, stealth mode, browser actions, and more.
```python Python theme={"system"}
result = client.extract.run(
url="https://www.example.com",
render=True
)
print(result.task_id)
```
See the [Extract docs](/nimble-sdk/web-tools/extract/quickstart) for the full
parameter list — geo-targeting, output formats, parsing schemas, and more.
### Search
Perform real-time web searches and get structured results:
```python Python theme={"system"}
result = client.search(
query="best web scraping tools",
country="US",
search_depth="standard",
full_content=True
)
print(result)
```
### Map
Discover all URLs within a domain or sitemap:
```python Python theme={"system"}
result = client.map(
URL="https://www.example.com",
sitemap="only"
)
print(result)
```
### Crawl
Recursively crawl and extract an entire website at scale:
```python Python theme={"system"}
result = client.crawl.run(
url="https://www.example.com",
limit=100
)
print(result)
```
### Extract Template
Run pre-built Extract Templates for structured data from popular platforms:
```python Python theme={"system"}
result = client.extract.templates.run(
template="amazon_pdp",
params={"asin": "B08N5WRWNW"}
)
print(result.data.parsing)
```
Browse the full template catalog with `client.extract.templates.list()`.
## Async Client
Use `AsyncNimble` for non-blocking, concurrent operations:
```python Python theme={"system"}
import asyncio
import os
from nimble_python import AsyncNimble
client = AsyncNimble(api_key=os.environ.get("NIMBLE_API_KEY"))
async def main():
result = await client.extract.run(url="https://www.example.com", render=True)
print(result.task_id)
asyncio.run(main())
```
### aiohttp Backend
For high-concurrency workloads, use the `aiohttp` HTTP backend (requires `pip install "nimble_python[aiohttp]"`):
```python Python theme={"system"}
import asyncio
import os
from nimble_python import AsyncNimble, DefaultAioHttpClient
async def main():
async with AsyncNimble(
api_key=os.environ.get("NIMBLE_API_KEY"),
http_client=DefaultAioHttpClient(),
) as client:
result = await client.extract.run(url="https://www.example.com", render=True)
print(result.task_id)
asyncio.run(main())
```
Always use `async with` when working with `DefaultAioHttpClient` to ensure
proper resource cleanup.
## Error Handling
The SDK maps API failures to typed exception classes:
```python Python theme={"system"}
import nimble_python
import os
from nimble_python import Nimble
client = Nimble(api_key=os.environ.get("NIMBLE_API_KEY"))
try:
result = client.extract.run(url="https://www.example.com")
except nimble_python.APIConnectionError as e:
print("Connection failed:", e)
except nimble_python.AuthenticationError as e:
print("Invalid API key — check NIMBLE_API_KEY")
except nimble_python.RateLimitError as e:
print("Rate limited — back off and retry:", e)
except nimble_python.APIStatusError as e:
print(f"API error {e.status_code}:", e.message)
```
| Exception | HTTP Status | When it occurs |
| - | - | - |
| `APIConnectionError` | — | Network failure or timeout |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `PermissionDeniedError` | 403 | Insufficient account permissions |
| `RateLimitError` | 429 | Request rate exceeded |
| `InternalServerError` | 500+ | Server-side error |
## Configuration
### Timeouts & Retries
The SDK retries automatically on connection errors and server failures. Defaults: **2 retries**, **3-minute timeout**.
```python Python theme={"system"}
import os
from nimble_python import Nimble
# Configure globally
client = Nimble(
api_key=os.environ.get("NIMBLE_API_KEY"),
max_retries=3,
timeout=60.0, # seconds
)
# Override per-request
result = client.with_options(max_retries=0).extract.run(
url="https://www.example.com"
)
```
### Logging
Enable SDK-level debug logging via environment variable:
```bash theme={"system"}
export NIMBLE_LOG=debug
```
Or configure programmatically:
```python Python theme={"system"}
import logging
logging.basicConfig()
logging.getLogger("nimble_python").setLevel(logging.DEBUG)
```
## Advanced
### Raw Response Access
Access HTTP headers and response metadata alongside the parsed result:
```python Python theme={"system"}
response = client.extract.with_raw_response.run(url="https://www.example.com")
print(response.headers.get("x-request-id"))
result = response.parse()
print(result.task_id)
```
### Streaming Response
Stream large response bodies instead of buffering them in memory:
```python Python theme={"system"}
with client.extract.with_streaming_response.run(url="https://www.example.com") as response:
for chunk in response.iter_bytes():
process(chunk)
```
## Next Steps
Full REST API documentation for all endpoints
Rendering, formats, stealth mode, and more
Real-time web search with structured results
Pre-built Extract Templates for popular platforms
# Efforts
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/efforts
Control how much work the agent invests in each run
Every run has an **effort** level. It controls how many angles the task is broken into, how many sources the agent reads, how many research rounds it runs, and how long it can take.
Set a default effort on the agent, and override it per run when needed.
## Effort levels
| Effort | Built for | Typical time | Price |
| - | - | - | - |
| `low` | Quick lookups and fact checks | 10–30 sec | \$0.025 / run |
| `medium` | Multi-source research | 1–3 min | \$0.10 / run |
| `high` | Deep cross-source research | 5–15 min | \$0.50 / run |
| `x-high` | Heavy multi-step tasks | 15–30 min | \$2.00 / run |
| `max` | Custom budget per run | 30 min–hours | Custom, **coming soon** |
`low` runs a single fast pass: search, read the best results, answer. Each level up widens the fan-out, adds research rounds, and raises the bar on source verification. `high` is the default.
## Pricing
Each level has one flat price. Billing is **runs × effort price**. Compute, retrieval, and storage are included.
Two guidelines:
* **Match effort to stakes.** A daily news sweep is `low`. A brief that feeds a pricing decision is `high`. Due diligence before a deal is `x-high`.
* **Use the trust report to right-size.** If a `medium` run returns all-`high` [confidence grades](/nimble-sdk/web-search-agents/trust), a higher tier is not needed.
## Set the effort
The agent carries a default. Any run can override it:
```python Python theme={"system"}
# default effort for every run of this agent
agent = nimble.agents.create(display_name="Pricing Watcher", effort="high")
# override for a single run
run = nimble.agents.runs.create(
agent.id,
input="Full pricing teardown of Datadog vs. Grafana Cloud, enterprise tiers included.",
effort="x-high",
)
```
```typescript TypeScript theme={"system"}
// default effort for every run of this agent
const agent = await nimble.agents.create({ display_name: "Pricing Watcher", effort: "high" });
// override for a single run
const run = await nimble.agents.runs.create(agent.id, {
input: "Full pricing teardown of Datadog vs. Grafana Cloud, enterprise tiers included.",
effort: "x-high",
});
```
```go Go theme={"system"}
// default effort for every run of this agent
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
DisplayName: param.NewOpt("Pricing Watcher"),
Effort: nimble.AgentNewParamsEffortHigh,
})
// override for a single run
run, err := client.Agents.Runs.New(ctx, agent.ID, nimble.AgentRunNewParams{
Input: "Full pricing teardown of Datadog vs. Grafana Cloud, enterprise tiers included.",
Effort: nimble.AgentRunNewParamsEffortXHigh,
})
```
```bash CLI theme={"system"}
# default effort for every run of this agent
nimble agents create --display-name "Pricing Watcher" --effort high
# override for a single run
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Full pricing teardown of Datadog vs. Grafana Cloud, enterprise tiers included." \
--effort x-high
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Full pricing teardown of Datadog vs. Grafana Cloud, enterprise tiers included.",
"effort": "x-high"
}'
```
[Templates](/nimble-sdk/web-search-agents/overview#start-from-a-template) ship with a tuned default. `company-profile` runs at `medium`; `competitive-intelligence` and `due-diligence` run at `x-high`.
## Max
**Coming soon.** `max` removes the ceiling: you set the budget per run, for 30-minute to hours-long tasks with the widest fan-out and deepest verification. For workloads that need it, [contact Nimble](https://nimbleway.com/contact-general/).
## Next Steps
Run your first task at the default effort
Use confidence grades to right-size effort
Research, Enrichment, and Dataset Building
# Agent
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/overview
Turn complex web research tasks into a repeatable API call, with an expert agent that improves every run
**Agents** turn complex web research tasks into repeatable API calls. Describe the task in plain language. The agent plans the work, searches and extracts across the live web, reasons over what it finds, and returns a cited answer or a structured dataset.
Generic search tools treat every use case the same. An Agent is an **expert you shape**: it learns your domain, remembers what worked, and improves with every run, while staying fully controllable and auditable.
Run a research task in the browser. No code, no setup
Zero to a cited answer in three API calls
## Input and output
One request in:
```json theme={"system"}
{ "input": "Compare the pricing and positioning of Datadog and Grafana Cloud." }
```
One deliverable out:
```text theme={"system"}
Datadog anchors the premium end of the market, with Pro pricing
from $15 per host per month [1]. Grafana Cloud undercuts it with
a free tier and usage-based pricing [2], positioning itself for
teams that want to start small...
[1] datadoghq.com/pricing: "Pro: starting at $15 per host, per month"
[2] grafana.com/pricing: "Start for free, pay as you go"
```
Each `[n]` marker resolves to a machine-readable claim with source URLs, verbatim excerpts, and a confidence grade. See [Trust](/nimble-sdk/web-search-agents/trust).
Run it yourself, [in the Playground](https://online.nimbleway.com/playground) or from code:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
run = nimble.agents.run(
input="Compare the pricing and positioning of Datadog and Grafana Cloud."
)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const run = await nimble.agents.run({
input: "Compare the pricing and positioning of Datadog and Grafana Cloud.",
});
```
```go Go theme={"system"}
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
run, err := client.Agents.Run(ctx, nimble.AgentRunParams{
Input: "Compare the pricing and positioning of Datadog and Grafana Cloud.",
})
```
```bash CLI theme={"system"}
nimble agents run \
--input "Compare the pricing and positioning of Datadog and Grafana Cloud."
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents/runs' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Compare the pricing and positioning of Datadog and Grafana Cloud."
}'
```
Runs are asynchronous. Poll for the result or stream progress events. The [Quickstart](/nimble-sdk/web-search-agents/quickstart) walks through the full loop.
## What you can build
Every agent works in one of three modes, set by its `use_case`:
**Answer hard questions.** Competitive intelligence, due diligence, market and sentiment analysis: cited briefs on demand.
**Complete your data.** Send CRM rows or product records with gaps, get them back filled, every added value cited.
**Discover from scratch.** One prompt in, a structured dataset out, every cell with its own evidence.
| Mode | Input | Output |
| - | - | - |
| **Research** | *"How is Acme positioned against its competitors?"* | A cited analyst brief |
| **Enrichment** | 500 CRM rows with missing funding data + a schema | The same 500 rows, gaps filled and cited |
| **Dataset Building** | *"Find all spas and salons in New York City"* + a schema | Fresh rows discovered from the live web |
Build the agent your workflow needs: a **Company Research Agent** that maintains living company profiles, a **Consumer Sentiment Agent** that reads reviews and forums, a **Lead Enrichment Agent** that completes your pipeline, or start from a [pre-built template](#start-from-a-template).
## Start from a template
Templates ship with goals, source guidance, a skill briefing, and output schemas already tuned. Create your own copy with one call and customize from there:
```python Python theme={"system"}
agent = nimble.agents.create(template="due-diligence")
```
```typescript TypeScript theme={"system"}
const agent = await nimble.agents.create({ template: "due-diligence" });
```
```go Go theme={"system"}
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
Template: param.NewOpt("due-diligence"),
})
```
```bash CLI theme={"system"}
nimble agents create --template due-diligence
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{"template": "due-diligence"}'
```
| Mode | Templates |
| - | - |
| **Research** | `competitive-intelligence` · `due-diligence` · `brand-intelligence` · `company-profile` · `price-comparison` · `real-estate-research` |
| **Enrichment** | `lead-enrichment` · `financial-intelligence` · `ecommerce-intelligence` |
| **Dataset Building** | `business-discovery` · `company-discovery` · `gtm-lead-discovery` · `open-positions-search` · `social-media-monitor` |
List them with [`GET /v2/agents/templates`](/api-reference/public-api/list-agent-templates). Each returns its full configuration, suggested prompts, and an example result.
## Why it's different
Generic web search has no idea what your agent is trying to accomplish. A Nimble Agent does, because it's **stateful, self-improving, and configurable to perfection**:
* **An expert, not a generic index.** The agent learns your use case through its `skill`, `goals`, and `sources`: what dimensions matter, what jargon means, which sources to trust, and what a good result looks like.
* **Self-improving.** **Memory** captures what worked (sources, retrieval paths, domain patterns) and feeds every future run. **Storage** keeps collected data and outputs so runs build on each other instead of starting from zero. Recurring tasks get faster, more accurate, and up to 50% cheaper.
* **Trustworthy.** Every run returns a [trust report](/nimble-sdk/web-search-agents/trust): per-claim citations with verbatim excerpts, graded confidence, and the full list of sources consulted, auditable end to end.
* **Full web access.** Agents run on Nimble's web tools: [Search](/nimble-sdk/web-tools/search), [Extract](/nimble-sdk/web-tools/extract/quickstart) with JS rendering and stealth mode, [Extract Templates](/nimble-sdk/web-tools/extract/template), and [Crawl](/nimble-sdk/web-tools/crawl), reaching data that generic tools can't.
## How it works
Plain language, like briefing an analyst. Optionally add a JSON Schema to get structured data back.
It breaks the task into checkable goals and search steps, informed by its skill and everything in memory.
The agent works across dozens to hundreds of pages, cross-checking conflicting sources and ranking them by authority.
A prose answer or JSON matching your schema, plus the trust report grading the evidence behind every claim.
## Configure it to perfection
`agents.run` works with zero configuration. For repeat work, create a persistent agent and tune every dimension:
| Field | What it does |
| - | - |
| `skill` | A briefing that makes the agent think like your analyst: what dimensions matter, what jargon means |
| `goals` | Checkable objectives the agent works through on every run |
| `sources` | Allow-lists, block-lists, and hints like *"official pricing pages over third-party summaries"* |
| `effort` | A dial from `low` (quick sweep) to `x-high` (exhaustive). See [Efforts](/nimble-sdk/web-search-agents/efforts) |
| `output_schema` | A JSON Schema. Answers arrive as data, every value cited. Must follow [output schema limits](/nimble-sdk/web-search-agents/use-cases/dataset-building#output-schema-limits) |
| `use_case` | `research`, `enrichment`, or `dataset_building` |
## Runs at any scale
Stream a single run's progress as [server-sent events](/api-reference/public-api/stream-agent-run-events), chain follow-up runs with `previous_interaction_id`, or hand the agent to [Jobs](/nimble-sdk/agentic/jobs) to run on a schedule across thousands of inputs with managed delivery.
Building AI agents? Nimble Agents are available as tools on the [Nimble MCP server](/integrations/mcp-server/mcp-server). Call them from Claude, Cursor, or any MCP client in production.
## Zero Data Retention
Agents support Zero Data Retention (ZDR) on enterprise plans. With ZDR enabled, your prompts, input data, and outputs are retained only within the window defined in your contract, and are never used for training or shared with any other customer. Retention is set per data type, including whether the agent keeps memory and storage across runs. [Contact Nimble](https://nimbleway.com/contact-general/) to enable ZDR for your account.
## Next steps
Run your first task in the browser
The full create → wait → result loop in code
Research, Enrichment, and Dataset Building in depth
Every endpoint, field, and response
# Quickstart
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/quickstart
Create a run, wait for completion, and retrieve the result
Every Agent task follows the same three steps: **create** a run, **wait** for it to complete, and **retrieve** the result. This page walks through the full loop.
You need an API key from the [Nimble Platform](https://online.nimbleway.com) and a client:
```bash Python theme={"system"}
pip install nimble_python
```
```bash TypeScript theme={"system"}
npm install @nimble-way/nimble-js
```
```bash Go theme={"system"}
go get github.com/Nimbleway/nimble-go@latest
```
```bash CLI theme={"system"}
npm install -g @nimble-way/nimble-cli
export NIMBLE_API_KEY="YOUR-API-KEY"
```
Send the task in plain language. `agents.run` generates an agent with default settings (`research` mode, `high` [effort](/nimble-sdk/web-search-agents/efforts)) and starts the run asynchronously:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
run = nimble.agents.run(
input=(
"Compare the pricing and positioning of Datadog, Grafana Cloud, "
"and New Relic for a mid-size SaaS team."
),
)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: process.env.NIMBLE_API_KEY });
const run = await nimble.agents.run({
input:
"Compare the pricing and positioning of Datadog, Grafana Cloud, " +
"and New Relic for a mid-size SaaS team.",
});
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
ctx := context.Background()
run, err := client.Agents.Run(ctx, nimble.AgentRunParams{
Input: "Compare the pricing and positioning of Datadog, Grafana Cloud, " +
"and New Relic for a mid-size SaaS team.",
})
if err != nil {
panic(err)
}
fmt.Println(run.ID, run.Status)
}
```
```bash CLI theme={"system"}
RUN=$(nimble agents run \
--input "Compare the pricing and positioning of Datadog, Grafana Cloud, and New Relic for a mid-size SaaS team.")
RUN_ID=$(echo "$RUN" | jq -r .id)
AGENT_ID=$(echo "$RUN" | jq -r .web_search_agent_id)
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents/runs' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Compare the pricing and positioning of Datadog, Grafana Cloud, and New Relic for a mid-size SaaS team."
}'
```
The response contains the run id and the generated agent's id. Both are needed to poll and fetch the result:
```json theme={"system"}
{
"id": "task_run_01j9x8...",
"web_search_agent_id": "wsa_01j9x8...",
"status": "queued",
"is_active": true,
"effort": "high",
"created_at": "2026-07-22T10:15:00Z"
}
```
Add an `output_schema` to the request to get structured JSON back instead of prose. See [Dataset Building](/nimble-sdk/web-search-agents/use-cases/dataset-building) and [output schema limits](/nimble-sdk/web-search-agents/use-cases/dataset-building#output-schema-limits).
Poll while `is_active` is true. Alternatively, create the run with `enable_events: true` and stream `GET .../runs/{run_id}/events` as [server-sent events](/api-reference/public-api/stream-agent-run-events):
```python Python theme={"system"}
import time
agent_id = run.web_search_agent_id
while run.is_active:
time.sleep(10)
run = nimble.agents.runs.get(run.id, agent_id=agent_id)
print(run.status) # completed
```
```typescript TypeScript theme={"system"}
const agentId = run.web_search_agent_id;
let polled = run;
while (polled.is_active) {
await new Promise((r) => setTimeout(r, 10000));
polled = await nimble.agents.runs.get(run.id, { agent_id: agentId });
}
console.log(polled.status); // completed
```
```go Go theme={"system"}
for run.IsActive {
time.Sleep(10 * time.Second)
polled, err := client.Agents.Runs.Get(ctx, run.ID, nimble.AgentRunGetParams{
AgentID: run.WebSearchAgentID,
})
if err != nil {
panic(err)
}
run.IsActive = polled.IsActive
}
```
```bash CLI theme={"system"}
nimble agents:runs get --agent-id "$AGENT_ID" --run-id "$RUN_ID"
# repeat while "is_active" is true
```
```bash cURL theme={"system"}
curl "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs/$RUN_ID" \
--header 'Authorization: Bearer '
```
```python Python theme={"system"}
result = nimble.agents.runs.result(run.id, agent_id=agent_id)
print(result.output.content) # the answer
print(result.output.trust.confidence) # high / medium / low
for claim in result.output.trust.claims:
print(claim.citations[0].url) # sources behind each claim
```
```typescript TypeScript theme={"system"}
const result = await nimble.agents.runs.result(run.id, { agent_id: agentId });
if ("output" in result) {
console.log(result.output.content); // the answer
console.log(result.output.trust.confidence); // high / medium / low
for (const claim of result.output.trust.claims) {
console.log(claim.citations[0].url); // sources behind each claim
}
}
```
```go Go theme={"system"}
result, err := client.Agents.Runs.Result(ctx, run.ID, nimble.AgentRunResultParams{
AgentID: run.WebSearchAgentID,
})
if err != nil {
panic(err)
}
fmt.Println(result.Output.Content.OfString) // the answer
fmt.Println(result.Output.Trust.Confidence) // high / medium / low
```
```bash CLI theme={"system"}
nimble agents:runs result --agent-id "$AGENT_ID" --run-id "$RUN_ID"
```
```bash cURL theme={"system"}
curl "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs/$RUN_ID/result" \
--header 'Authorization: Bearer '
```
The output contains the answer and its trust report:
```text theme={"system"}
Datadog anchors the premium end of the market, with Pro pricing from
$15 per host per month [1]. Grafana Cloud undercuts it with a free
tier and usage-based pricing [2], positioning itself for teams that
want to start small...
```
Every `[n]` maps to a claim in `trust.claims`, with source URLs, verbatim excerpts, and a confidence grade. See [Trust](/nimble-sdk/web-search-agents/trust).
Fetching the result while the run is still active returns `409`. Keep polling. A `failed` run returns `422` with error details.
## Run lifecycle
| Status | Meaning |
| - | - |
| `queued` | Accepted, waiting to start |
| `running` | The agent is working, `is_active` is `true` |
| `completed` | The result is ready at `.../result` |
| `failed` | The run stopped with an error. `.../result` returns `422` with details |
| `cancelled` | The run was stopped before completion |
## Follow-up runs
Every run carries an `interaction_id`. Pass it as `previous_interaction_id` on the next run to continue the same task with full context: a follow-up question, a refinement, or a drill-down:
```python Python theme={"system"}
followup = nimble.agents.runs.create(
agent_id,
input="Now break down the enterprise tiers only.",
previous_interaction_id=run.interaction_id,
)
```
```typescript TypeScript theme={"system"}
const followup = await nimble.agents.runs.create(agentId, {
input: "Now break down the enterprise tiers only.",
previous_interaction_id: run.interaction_id,
});
```
```go Go theme={"system"}
followup, err := client.Agents.Runs.New(ctx, agentID, nimble.AgentRunNewParams{
Input: "Now break down the enterprise tiers only.",
PreviousInteractionID: param.NewOpt(run.InteractionID),
})
```
```bash CLI theme={"system"}
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Now break down the enterprise tiers only." \
--previous-interaction-id "$INTERACTION_ID"
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Now break down the enterprise tiers only.",
"previous_interaction_id": "'$INTERACTION_ID'"
}'
```
## Reuse an agent by name
Passing no identity to `agents.run` (as in Step 1) creates a fresh, disposable agent on every call, no memory carries over between runs. Add `agent_name` to get a stable, reusable agent without a separate create step: the first call with a new name creates the agent, every later call with the same name routes to it.
```python Python theme={"system"}
run = nimble.agents.run(
agent_name="saas-pricing-watcher",
input="Compare the pricing and positioning of Datadog and Grafana Cloud.",
)
# Later calls with the same name reuse the same agent and its memory.
followup = nimble.agents.run(
agent_name="saas-pricing-watcher",
input="Now check New Relic too.",
)
```
```typescript TypeScript theme={"system"}
const run = await nimble.agents.run({
agent_name: "saas-pricing-watcher",
input: "Compare the pricing and positioning of Datadog and Grafana Cloud.",
});
// Later calls with the same name reuse the same agent and its memory.
const followup = await nimble.agents.run({
agent_name: "saas-pricing-watcher",
input: "Now check New Relic too.",
});
```
```bash CLI theme={"system"}
nimble agents run \
--agent-name "saas-pricing-watcher" \
--input "Compare the pricing and positioning of Datadog and Grafana Cloud."
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents/runs' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"agent_name": "saas-pricing-watcher",
"input": "Compare the pricing and positioning of Datadog and Grafana Cloud."
}'
```
`use_case` and `skill` can be set the same way, on the call that creates the agent:
```python Python theme={"system"}
run = nimble.agents.run(
agent_name="saas-pricing-watcher",
input="Compare the pricing and positioning of Datadog and Grafana Cloud.",
use_case="research",
skill="SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages.",
)
```
```typescript TypeScript theme={"system"}
const run = await nimble.agents.run({
agent_name: "saas-pricing-watcher",
input: "Compare the pricing and positioning of Datadog and Grafana Cloud.",
use_case: "research",
skill: "SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages.",
});
```
`use_case` is locked once the agent exists: a later run against the same agent must either omit `use_case` or pass the value it already has, a different value returns `422`. `skill`, `sources`, and `output_schema` work the other way, they stay overridable per run (see [below](#override-per-run-vs-persist)) even after the agent is created.
## Create a persistent agent
`agents.run` without `agent_name` generates a one-off agent with default settings. For full control over an agent's lifecycle, create a persistent agent explicitly with a skill, goals, sources, and a default effort — equivalent to what `agent_name` does implicitly above, but you own the `agent_id` yourself instead of addressing the agent by name. A persistent agent keeps its memory across runs and improves over time:
```python Python theme={"system"}
agent = nimble.agents.create(
display_name="SaaS Pricing Watcher",
skill="SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages.",
goals=[
"Compare pricing tiers with plan names and price ranges",
"Call out pricing changes announced in the last quarter",
],
sources={
"prioritize": "official vendor pricing pages over third-party summaries",
"block": [{"title": "Aggregators", "domains": ["g2.com"]}],
},
effort="x-high",
)
```
```typescript TypeScript theme={"system"}
const agent = await nimble.agents.create({
display_name: "SaaS Pricing Watcher",
skill: "SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages.",
goals: [
"Compare pricing tiers with plan names and price ranges",
"Call out pricing changes announced in the last quarter",
],
sources: {
prioritize: "official vendor pricing pages over third-party summaries",
block: [{ title: "Aggregators", domains: ["g2.com"] }],
},
effort: "x-high",
});
```
```go Go theme={"system"}
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
DisplayName: param.NewOpt("SaaS Pricing Watcher"),
Skill: param.NewOpt("SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages."),
Goals: []string{
"Compare pricing tiers with plan names and price ranges",
"Call out pricing changes announced in the last quarter",
},
Sources: nimble.AgentNewParamsSources{
Prioritize: param.NewOpt("official vendor pricing pages over third-party summaries"),
Block: []nimble.AgentNewParamsSourcesBlock{
{Title: "Aggregators", Domains: []string{"g2.com"}},
},
},
Effort: nimble.AgentNewParamsEffortXHigh,
})
```
```bash CLI theme={"system"}
nimble agents create \
--display-name "SaaS Pricing Watcher" \
--skill "SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages." \
--goal "Compare pricing tiers with plan names and price ranges" \
--goal "Call out pricing changes announced in the last quarter" \
--sources '{prioritize: "official vendor pricing pages over third-party summaries", block: [{title: "Aggregators", domains: ["g2.com"]}]}' \
--effort x-high
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/agents' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"display_name": "SaaS Pricing Watcher",
"skill": "SaaS pricing analyst: compare tiers, flag recent changes, cite vendor pages.",
"goals": [
"Compare pricing tiers with plan names and price ranges",
"Call out pricing changes announced in the last quarter"
],
"sources": {
"prioritize": "official vendor pricing pages over third-party summaries",
"block": [{"title": "Aggregators", "domains": ["g2.com"]}]
},
"effort": "x-high"
}'
```
## Override per run vs. persist
Once an agent exists (via `agent_name` or an explicit `agent_id`), a run can pass `sources`, `output_schema`, and `skill` to override that agent's defaults for **that run only**, the agent's own stored config is unchanged. To persist a change instead, update the agent directly with `agents.update(agent_id, ...)`.
`use_case` doesn't follow this pattern: it's fixed once the agent is created, not overridable per run.
## Next Steps
Per-claim citations and confidence grades
Research, Enrichment, and Dataset Building
Every endpoint, field, and response
# Trust
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/trust
Per-claim citations, verbatim evidence, and graded confidence on every answer
Every run returns a **trust report** alongside the output. It accounts for the evidence behind each claim in the answer:
* **Every claim is cited.** Each statement in a prose answer (and each value in a structured one) links to the exact URLs it came from, with verbatim excerpts from the page.
* **Every claim is graded.** `high`, `medium`, or `low` confidence, with the reasoning included.
* **Every source is classified.** Sources are typed (`official`, `news`, `social`, `academic`) and ranked `primary` or `secondary` for the claim they support.
## Example
A prose answer comes back with numbered callouts:
```text theme={"system"}
Datadog anchors the premium end of the market, with Pro pricing starting
at $15 per host per month [1]. Grafana Cloud takes the opposite approach:
a free tier and usage-based pricing aimed at teams that want to start
small [2]...
```
Each `[n]` resolves to a claim in the trust report:
```json theme={"system"}
{
"confidence": "high",
"reasoning": "Pricing claims verified against official vendor pages; positioning corroborated by independent sources.",
"sources": [
{
"url": "https://www.datadoghq.com/pricing/",
"title": "Datadog Pricing",
"type": "primary",
"source_category": "official"
},
{
"url": "https://grafana.com/pricing/",
"title": "Grafana Cloud Pricing",
"type": "primary",
"source_category": "official"
}
],
"claims": [
{
"callout": 1,
"confidence": "high",
"reasoning": "Backed by a primary source (official)",
"citations": [
{
"url": "https://www.datadoghq.com/pricing/",
"title": "Datadog Pricing",
"excerpts": ["Pro: starting at $15 per host, per month"],
"source_category": "official"
}
]
},
{
"callout": 2,
"confidence": "medium",
"reasoning": "Backed by a secondary source (news)",
"citations": [
{
"url": "https://devopsweekly.example.com/observability-pricing",
"title": "The state of observability pricing",
"excerpts": ["Grafana Cloud's free tier remains the entry point for smaller teams"],
"source_category": "news"
}
]
}
]
}
```
Each claim carries the value from the answer, the page it came from, and the exact sentence on that page that supports it. Note that claim `[2]` grades `medium`, supported by a single news source, so the report tells you exactly which claims to double-check before acting. Use it to verify the agent's work, or pipe the citations into your own review UI.
## Confidence rules
Grades follow fixed rules, applied the same way to every claim in every run:
* **Primary source → `high`.** Any citation from an `official` source (or a `news`/`social`/`academic` source when that is the kind of evidence the task calls for) grades the claim `high`.
* **Independent corroboration → `high`.** With no primary source, secondary citations from two or more different domains also grade `high`. Two articles from the same outlet do not corroborate each other. Independence means different domains.
* **Single secondary source → `medium`.** Supported, but by one source only.
* **No usable citation → `low`.** A value with no citation, or one that failed schema validation, is graded `low`.
The `reasoning` field states which rule applied: *"Backed by a primary source (official)"*, *"Supported by 3 independent sources"*, *"Backed by a secondary source (news)"*.
## Trust for structured outputs
With an [output schema](/nimble-sdk/web-search-agents/use-cases/dataset-building), claims are keyed by **JSON path** instead of callout number. Every value in a generated dataset carries its own evidence:
```json theme={"system"}
{
"path": "$.vendors[0].market_share",
"confidence": "medium",
"reasoning": "Backed by a secondary source (aggregator)",
"citations": [
{ "url": "https://...", "excerpts": ["Datadog holds an estimated ..."] }
]
}
```
Structured trust also covers missing data:
* **Verified absence.** A `null` value backed by citations grades `high`: the sources show the data does not exist (a company with no funding history, a product with no public price).
* **Schema validation.** A value that does not match the output schema is graded `low`, so structural errors are always flagged.
### Overall confidence for datasets
A structured run's overall confidence is computed from two measurements, and takes the lower of the two:
| Measurement | What it asks |
| - | - |
| **Fill rate** | How many of the cells the schema asked for were filled? |
| **Claim-trust ratio** | Of the researched cells, how many graded `high` or `medium`? |
The `reasoning` states which measurement set the grade: *"High confidence - cells fill rate is 96% and trust per claim ratio is 91%"*. Values supplied in `input_data` (`pre_existing`, in [enrichment](/nimble-sdk/web-search-agents/use-cases/enrichment) runs) are excluded. Only researched values are graded.
## Confidence grades
| Grade | Meaning |
| - | - |
| `high` | Corroborated by authoritative sources, safe to act on |
| `medium` | Supported by weaker or fewer sources, spot-check before relying on it |
| `low` | Best available signal; treat as a lead, not a fact |
| `pre_existing` | Supplied in `input_data`: carried over, not researched |
The run also carries an **overall** confidence and reasoning for the answer as a whole.
`trust.claims` is designed to be machine-consumed: route `low`-confidence values to human review, accept `high` ones automatically, and render citations inline in your product.
## Next Steps
See a trust report on your own task
How `pre_existing` works in enrichment runs
The full trust object, field by field
# Dataset Building
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/use-cases/dataset-building
One prompt in, a verified dataset out, every cell cited
**Dataset Building** agents create structured datasets from data scattered across the web. Describe what you're looking for, define the row shape, and the agent discovers, verifies, and structures the results: no scraper to build, no site list to maintain, no deduplication pipeline. [Every cell arrives cited](/nimble-sdk/web-search-agents/trust).
## Example Request
The `business-discovery` template is tuned for local-business datasets: verified addresses, phone numbers, and ratings:
```python Python theme={"system"}
agent = nimble.agents.create(template="business-discovery")
run = nimble.agents.runs.create(
agent.id,
input=(
"Find all spas and salons in New York City, real addresses, "
"phone numbers, and ratings from actual review sites."
),
)
```
```typescript TypeScript theme={"system"}
const agent = await nimble.agents.create({ template: "business-discovery" });
const run = await nimble.agents.runs.create(agent.id, {
input:
"Find all spas and salons in New York City, real addresses, " +
"phone numbers, and ratings from actual review sites.",
});
```
```go Go theme={"system"}
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
Template: param.NewOpt("business-discovery"),
})
run, err := client.Agents.Runs.New(ctx, agent.ID, nimble.AgentRunNewParams{
Input: "Find all spas and salons in New York City, real addresses, " +
"phone numbers, and ratings from actual review sites.",
})
```
```bash CLI theme={"system"}
AGENT_ID=$(nimble agents create --template business-discovery | jq -r .id)
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Find all spas and salons in New York City, real addresses, phone numbers, and ratings from actual review sites."
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Find all spas and salons in New York City, real addresses, phone numbers, and ratings from actual review sites."
}'
```
## Example Response
The result is `type: "json"`: an array of rows matching the template's schema, each with a cited source:
```json theme={"system"}
{
"type": "json",
"content": [
{
"business_name": "Haven Spa",
"address": "150 Mercer St, New York, NY 10012",
"phone_number": "+1 212-555-0181",
"website": "https://havenspanyc.com",
"rating": "4.6",
"review_count": "412",
"category": "Day spa",
"source_url": "https://www.yelp.com/biz/haven-spa-new-york"
}
]
}
```
Each value carries a per-path claim in the trust report:
```json theme={"system"}
{
"path": "$[0].phone_number",
"confidence": "high",
"reasoning": "Supported by 2 independent sources",
"citations": [
{ "url": "https://www.yelp.com/biz/haven-spa-new-york", "excerpts": ["(212) 555-0181"] },
{ "url": "https://havenspanyc.com/contact", "excerpts": ["Call us: (212) 555-0181"] }
]
}
```
The dataset gets a deterministic overall grade, the lower of its **cell fill rate** and its **per-claim trust ratio**, stated in the run's trust reasoning: *"High confidence - cells fill rate is 96% and trust per claim ratio is 91%"*. A verifiably empty cell (a business with no website) comes back as a cited `null` graded `high`: [verified absence](/nimble-sdk/web-search-agents/trust#trust-for-structured-outputs).
## Custom output schema
Define your own row shape on the agent or per run. Filters in the input are treated as hard constraints. The `company-discovery` template's first goal is *"Treat user filters (location, vertical, size, funding) as hard constraints, never dilute to inflate volume."*
Schemas must follow the hard limits below. Invalid schemas return **422** at request time.
```python Python theme={"system"}
run = nimble.agents.runs.create(
agent.id,
input=(
"Find SF tech companies that have adopted AI agents and have active "
"engineering teams, potential CRM leads, explaining why each qualifies."
),
output_schema={
"type": "object",
"properties": {
"leads": {
"type": "array",
"items": {
"type": "object",
"properties": {
"company": {"type": "string"},
"why_qualified": {"type": "string"},
"engineering_signal": {"type": "string"},
},
},
}
},
},
)
```
```typescript TypeScript theme={"system"}
const run = await nimble.agents.runs.create(agent.id, {
input:
"Find SF tech companies that have adopted AI agents and have active " +
"engineering teams, potential CRM leads, explaining why each qualifies.",
output_schema: {
type: "object",
properties: {
leads: {
type: "array",
items: {
type: "object",
properties: {
company: { type: "string" },
why_qualified: { type: "string" },
engineering_signal: { type: "string" },
},
},
},
},
},
});
```
```go Go theme={"system"}
run, err := client.Agents.Runs.New(ctx, agent.ID, nimble.AgentRunNewParams{
Input: "Find SF tech companies that have adopted AI agents and have active " +
"engineering teams, potential CRM leads, explaining why each qualifies.",
OutputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"leads": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"company": map[string]any{"type": "string"},
"why_qualified": map[string]any{"type": "string"},
"engineering_signal": map[string]any{"type": "string"},
},
},
},
},
},
})
```
```bash CLI theme={"system"}
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Find SF tech companies that have adopted AI agents and have active engineering teams, potential CRM leads, explaining why each qualifies." \
--output-schema '{type: object, properties: {leads: {type: array, items: {type: object, properties: {company: {type: string}, why_qualified: {type: string}, engineering_signal: {type: string}}}}}}'
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Find SF tech companies that have adopted AI agents and have active engineering teams, potential CRM leads, explaining why each qualifies.",
"output_schema": {
"type": "object",
"properties": {
"leads": {
"type": "array",
"items": {
"type": "object",
"properties": {
"company": {"type": "string"},
"why_qualified": {"type": "string"},
"engineering_signal": {"type": "string"}
}
}
}
}
}
}'
```
## Output schema limits
### Allowed roots
The root must be one of:
* An **object** with non-empty `properties`
* An **array of objects** (`items.type` must be `"object"` with `properties`)
Omit `output_schema` (or pass `{}`) to let the generator infer the shape from the prompt.
### Supported examples
**Object root with nullable fields:**
```json theme={"system"}
{
"type": "object",
"properties": {
"company_name": { "type": "string" },
"website": { "type": ["string", "null"] }
}
}
```
**Array root (table shape):**
```json theme={"system"}
{
"type": "array",
"items": {
"type": "object",
"properties": {
"company_name": { "type": "string" },
"hq_city": { "type": ["string", "null"] }
}
}
}
```
**Nested array of objects:**
```json theme={"system"}
{
"type": "object",
"properties": {
"key_contacts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": ["string", "null"] }
}
}
}
}
}
```
### Rejected patterns
The API returns **422** when a schema violates these hard limits:
| Limit | Rule |
| - | - |
| Root shape | No scalar roots, empty objects, or root `anyOf` |
| Array root | Must be an array of objects, not primitives |
| Nullable fields | Use `{"type": ["string", "null"]}`, not standalone `{"type": "null"}` |
| Nesting depth | At most **5** levels (objects and arrays count) |
| Property count | At most **100** properties across all levels |
| Enum size | At most **500** enum values total; enums with more than 250 values are also capped at 7,500 combined characters |
| Unsupported keywords | `format`, `pattern`, `minLength`, `maxLength`, `minimum`, `maximum`, `multipleOf`, `minItems`, `maxItems`, `uniqueItems`, `minProperties`, `maxProperties`, `patternProperties`, `propertyNames`, `contains`, `minContains`, `maxContains`, `unevaluatedItems`, `unevaluatedProperties` |
Move validation constraints into field `description` text instead of JSON Schema keywords.
**Unsupported examples:**
```json theme={"system"}
{ "type": "array", "items": { "type": "string" } }
```
```json theme={"system"}
{
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" }
}
}
```
```json theme={"system"}
{
"type": "object",
"properties": {
"x": { "type": "null" }
}
}
```
```json theme={"system"}
{
"type": "object",
"properties": { "name": { "type": "string" } },
"anyOf": [{ "required": ["name"] }]
}
```
### Still accepted
These are **not** hard-failed:
* `required` arrays on objects
* `additionalProperties: false`
* Nested `anyOf` / `oneOf` for nullability (not at the root)
* Enums within the size limits above
* Nested arrays of objects or primitives under an object root
### Field maps are invalid
Each field must be a JSON Schema subschema, not a bare type string:
```json theme={"system"}
{ "properties": { "city": { "type": "string" } } }
```
Not:
```json theme={"system"}
{ "city": "string" }
```
### Agent generation behavior
When creating or updating an Agent, the design path can infer and sanitize a schema from your goals and template. The generator strips unsupported keywords and fixes common mistakes (such as standalone `null`) before validation.
When you pass an explicit `output_schema` on create, update, or run, it is validated immediately. Fix the schema and retry on **422**.
### Common validation errors
| Error fragment | Cause |
| - | - |
| `must not use "anyOf"` | Root-level `anyOf` |
| `array of objects` | Root array whose `items` is not an object |
| `standalone "null" type` | Property typed as `"null"` alone |
| `unsupported keyword(s)` | A rejected keyword such as `format` or `pattern` |
| `nesting depth` | More than 5 nested object/array levels |
| `maximum of 100 properties` | Too many properties across the schema |
| `non-empty "properties"` | Object root with missing or empty `properties` |
| `declares nothing to write` | Field map syntax instead of proper subschemas |
Example **422** response:
```json theme={"system"}
{
"detail": [
{
"type": "value_error",
"loc": ["body", "output_schema"],
"msg": "Value error, output_schema uses unsupported keyword(s): format. Remove them (or move constraints into field descriptions).",
"input": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" }
}
}
}
]
}
```
## Run it on a schedule
A dataset run produces a snapshot. To turn it into a recurring feed, point a [Job](/nimble-sdk/agentic/jobs) at the same agent with a list of inputs (one per market, category, or account), and Nimble runs it on a schedule and delivers results to S3, Databricks, or your warehouse.
## Templates
| Template | Built for |
| - | - |
| `business-discovery` | Local businesses with verified contact details and ratings |
| `company-discovery` | Companies matching hard filters: location, vertical, size, funding |
| `gtm-lead-discovery` | Qualified sales leads with the reason each qualifies |
| `open-positions-search` | Live job openings across companies and markets |
| `social-media-monitor` | Structured social activity around brands and topics |
Research, Enrichment, and Dataset Building side by side
# Enrichment
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/use-cases/enrichment
Send rows with gaps, get them back complete, every added value cited
**Enrichment** agents complete the data you already have. Send partial records in `input_data` (CRM leads missing funding stages, product rows missing specs), and the agent researches each gap on the live web and fills it. Your values are treated as ground truth and passed through untouched. Only the blanks get researched.
```text theme={"system"}
IN "company_name": "Acme Corp"
"headquarters": null
"key_contacts": null
── the agent researches only what's missing ──
OUT "company_name": "Acme Corp" · yours, untouched
"headquarters": "Shelton, CT" · cited: acme.com
"key_contacts": [{"name": "..."}] · cited: acme.com/leadership
```
## Example Request
An enrichment run needs the rows (`input_data`) and the shape (`output_schema`, on the request or the agent). The `lead-enrichment` template ships a schema covering company identity, headquarters, decision-makers, and buying signals:
```python Python theme={"system"}
agent = nimble.agents.create(template="lead-enrichment")
run = nimble.agents.runs.create(
agent.id,
input="Fill in the missing fields for these companies.",
input_data=[
{"company_name": "Nimble", "headquarters": None, "key_contacts": None},
{"company_name": "Bright Data", "headquarters": None, "key_contacts": None},
],
)
```
```typescript TypeScript theme={"system"}
const agent = await nimble.agents.create({ template: "lead-enrichment" });
const run = await nimble.agents.runs.create(agent.id, {
input: "Fill in the missing fields for these companies.",
input_data: [
{ company_name: "Nimble", headquarters: null, key_contacts: null },
{ company_name: "Bright Data", headquarters: null, key_contacts: null },
],
});
```
```go Go theme={"system"}
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
Template: param.NewOpt("lead-enrichment"),
})
run, err := client.Agents.Runs.New(ctx, agent.ID, nimble.AgentRunNewParams{
Input: "Fill in the missing fields for these companies.",
InputData: nimble.AgentRunNewParamsInputDataUnion{
OfMapOfAnyMap: []map[string]any{
{"company_name": "Nimble", "headquarters": nil, "key_contacts": nil},
{"company_name": "Bright Data", "headquarters": nil, "key_contacts": nil},
},
},
})
```
```bash CLI theme={"system"}
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Fill in the missing fields for these companies." \
--input-data '[{company_name: "Nimble", headquarters: null, key_contacts: null}, {company_name: "Bright Data", headquarters: null, key_contacts: null}]'
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Fill in the missing fields for these companies.",
"input_data": [
{"company_name": "Nimble", "headquarters": null, "key_contacts": null},
{"company_name": "Bright Data", "headquarters": null, "key_contacts": null}
]
}'
```
`input_data` accepts a list of partial rows (or a single object) mirroring the output schema. An `output_schema` (on the request or the agent) is required.
## Example Response
The result is `type: "json"`: your rows back, gaps filled:
```json theme={"system"}
{
"type": "json",
"content": [
{
"company_name": "Nimble",
"website": "https://www.nimbleway.com",
"headquarters": "New York, NY, United States",
"key_contacts": [
{ "name": "Uri Knorovich", "title": "Co-founder & CEO", "seniority": "c_suite" }
],
"buying_signals": ["Launched the Agents API (July 2026)"],
"summary": "Nimble is an AI-native web data platform providing search, extraction, and research agent APIs."
}
],
"trust": {
"confidence": "high",
"reasoning": "High confidence - cells fill rate is 100% and trust per claim ratio is 94%",
"claims": [
{
"path": "$[0].company_name",
"confidence": "pre_existing",
"reasoning": "Provided in input_data"
},
{
"path": "$[0].headquarters",
"confidence": "high",
"reasoning": "Backed by a primary source (official)",
"citations": [
{
"url": "https://www.nimbleway.com/about",
"excerpts": ["Headquartered in New York"],
"source_category": "official"
}
]
}
]
}
}
```
## Trust in enrichment runs
Every value in the result carries a claim per JSON path in the [trust report](/nimble-sdk/web-search-agents/trust). The grades distinguish your data from researched data:
| Confidence | Meaning in an enrichment run |
| - | - |
| `pre_existing` | Came from your `input_data`, carried over unchanged |
| `high` / `medium` / `low` | Researched, with citations and excerpts |
For example, `$[0].headquarters` carries a citation and a `high` grade, while `$[0].company_name` is marked `pre_existing`. Route low-confidence fills to review and sync the rest into your CRM, with source URLs alongside.
Two additional rules:
* The run's overall grade counts only researched values. `pre_existing` data is passed through, never graded.
* A gap the agent could not fill comes back as a cited `null` ([verified absence](/nimble-sdk/web-search-agents/trust#trust-for-structured-outputs)), distinguishing "no funding data exists" from "not found".
## Use cases
* **CRM hygiene**: complete funding, headcount, and tech-stack fields across the pipeline, with buying signals like recent hiring flagged (`lead-enrichment`).
* **Financial datasets**: fill valuation, investor, and filing gaps from primary sources (`financial-intelligence`).
* **Product catalogs**: complete specs, pricing, and availability across competitor listings (`ecommerce-intelligence`).
To enrich thousands of rows on a schedule, hand the same agent to [Jobs](/nimble-sdk/agentic/jobs): CSV or Parquet in, enriched rows delivered to your warehouse.
How per-path claims, citations, and `pre_existing` fit together
# Research
Source: https://docs.nimbleway.com/nimble-sdk/web-search-agents/use-cases/research
Competitive intelligence, due diligence, and market analysis with cited briefs
**Research** agents answer complex questions: how a company is positioned, what the risks are in a deal, what changed in a market last quarter. The agent plans the task, works dozens of sources, and returns a brief with [every claim cited](/nimble-sdk/web-search-agents/trust).
## Example Request
A due-diligence task using the `due-diligence` template:
```python Python theme={"system"}
agent = nimble.agents.create(template="due-diligence")
run = nimble.agents.runs.create(
agent.id,
input=(
"Research Acme Corp before a deal or investment: financial health, "
"leadership team, legal and regulatory history, and key risks."
),
)
```
```typescript TypeScript theme={"system"}
const agent = await nimble.agents.create({ template: "due-diligence" });
const run = await nimble.agents.runs.create(agent.id, {
input:
"Research Acme Corp before a deal or investment: financial health, " +
"leadership team, legal and regulatory history, and key risks.",
});
```
```go Go theme={"system"}
agent, err := client.Agents.New(ctx, nimble.AgentNewParams{
Template: param.NewOpt("due-diligence"),
})
run, err := client.Agents.Runs.New(ctx, agent.ID, nimble.AgentRunNewParams{
Input: "Research Acme Corp before a deal or investment: financial health, " +
"leadership team, legal and regulatory history, and key risks.",
})
```
```bash CLI theme={"system"}
AGENT_ID=$(nimble agents create --template due-diligence | jq -r .id)
nimble agents:runs create \
--agent-id "$AGENT_ID" \
--input "Research Acme Corp before a deal or investment: financial health, leadership team, legal and regulatory history, and key risks."
```
```bash cURL theme={"system"}
curl -X POST "https://sdk.nimbleway.com/v2/agents/$AGENT_ID/runs" \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"input": "Research Acme Corp before a deal or investment: financial health, leadership team, legal and regulatory history, and key risks."
}'
```
## Example Response
The result is a brief with numbered callouts, plus a trust report grading every claim and quoting the page it came from:
```json theme={"system"}
{
"type": "text",
"content": "Acme Corp is in stable financial health. Net sales for fiscal 2025 were $194 million, up 4% year over year [1]. Leadership is stable around a long-tenured CEO [2]. The key risks are customer concentration: the five largest customers account for a significant share of net sales [3], and pending litigation disclosed in the latest 10-K [4]...",
"trust": {
"confidence": "high",
"reasoning": "Financial and risk claims backed by SEC filings; leadership confirmed by the latest proxy statement.",
"claims": [
{
"callout": 1,
"confidence": "high",
"reasoning": "Backed by a primary source (official)",
"citations": [
{
"url": "https://www.sec.gov/.../acme-10k-2025.htm",
"title": "Acme Corp Form 10-K",
"excerpts": ["Net sales in 2025 were $194.0 million, an increase of 4%"],
"source_category": "official"
}
]
}
]
}
}
```
A `low`-confidence flag marks the claims that need human review.
## Configuration
* **Goals** define the requirements. *"Provides an overall sentiment score with a clear methodology"*, *"Includes recent product announcements from the past 6 months"*. The agent checks each on every run.
* **Skill** defines the analysis approach. The `competitive-intelligence` template ships a full analyst briefing: pricing architecture, ICP, GTM motion, differentiators. Write your own to match how your team analyzes.
* **[Effort](/nimble-sdk/web-search-agents/efforts)** sets the depth. `low` for a daily news sweep, `x-high` for pre-deal diligence.
* **Sources** set the evidence bar. *"prioritize: SEC filings and official statements"* controls which sources the agent trusts, not just which it reads.
## Templates
| Template | Built for |
| - | - |
| `competitive-intelligence` | Pricing, positioning, and feature comparison across competitors |
| `due-diligence` | Pre-deal company investigation: financials, leadership, legal, risk |
| `brand-intelligence` | Sentiment and themes across social and news, with incidents flagged |
| `company-profile` | Fast, sourced company one-pagers |
| `price-comparison` | Cross-retailer price and availability checks |
| `real-estate-research` | Property and market research |
The create → wait → retrieve flow works for every research agent
# Crawl
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/crawl
Extract structured data from entire websites automatically
Nimble **Crawl** (async) systematically visits and extracts content from entire websites. Give it a starting URL, and it automatically discovers pages through sitemaps and internal links, then extracts clean structured data from every page it visits.
## Quick Start
### Example Request
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
limit=10
)
print(f"Crawl started with ID: {result.crawl_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
limit: 10,
});
console.log(`Crawl started with ID: ${result.crawl_id}`);
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
"github.com/Nimbleway/nimble-go/packages/param"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
result, err := client.Crawl.Run(context.Background(), nimble.CrawlRunParams{
URL: "https://www.nimbleway.com",
Limit: param.NewOpt(int64(10)),
})
if err != nil {
panic(err)
}
fmt.Println("Crawl started with ID:", result.CrawlID)
}
```
```bash CLI theme={"system"}
nimble crawl run --url "https://www.nimbleway.com" --limit 10
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"limit": 10
}'
```
### Example Response
```json theme={"system"}
{
"crawl_id": "e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f",
"name": null,
"url": "https://www.nimbleway.com",
"status": "queued",
"account_name": "your-account",
"created_at": "2026-02-09T23:15:40.785Z",
"updated_at": "2026-02-09T23:15:40.785Z",
"completed_at": null,
"crawl_options": {
"sitemap": "include",
"crawl_entire_domain": false,
"limit": 10,
"max_discovery_depth": 5,
"ignore_query_parameters": false,
"allow_external_links": false,
"allow_subdomains": false
},
"extract_options": null
}
```
## How it works
Provide a starting URL and configure crawl options (limits, filters,
extraction settings)
* Returns immediately with a `crawl_id` to track progress - The crawl runs
in the background on Nimble's infrastructure - Optional: Configure webhooks
to receive real-time notifications
* Reads sitemaps and follows internal links - Creates individual tasks for
each discovered URL - Extracts content from pages as they're visited -
Status updates live: track `pending`, `completed`, and `failed` counts
* Poll crawl status to monitor progress - Fetch extracted content for
completed tasks using `task_id` - Results persist after crawl completes for
later retrieval
## Parameters
Supported input parameters:
The starting point for your crawl. The crawler will begin here and discover other pages from this URL.
**Example:** `https://www.nimbleway.com`
Give your crawl a memorable name. This helps you identify it later when you have multiple crawls running.
**Example:** `my-zillow-crawl`
Stop the crawl after finding this many pages.
* Min: `1`
* Max: `10000`
* Default: `5000`
Automatically extract content from each page as you crawl it. Accepts all [Extract API](/nimble-sdk/web-tools/extract/quickstart) options.
\*\*Example: \*\*
```json theme={"system"}
{
"extract_options":{
"driver":"vx10",
"parse":true,
"formats": ["html", "markdown"]
}
}
```
Decide how to use the website's sitemap for discovering pages.
**Options:**
* `include` (default) - Use both the sitemap and discovered links
* `only` - Just use the sitemap (fastest)
* `skip` - Ignore the sitemap and only follow links
Let the crawler explore the entire domain, not just pages "under" your starting URL.
For example, if you start at `/blog`, enabling this lets it also crawl `/about` and `/contact`.
Allow the crawler to follow links to subdomains.
For example, from `www.example.com` to `blog.example.com` or `shop.example.com`.
Only crawl pages whose URLs match these regex patterns.
**Example:** `["/blog/.*", "/articles/.*"]`
Skip pages whose URLs match these regex patterns.
**Example:** `[".*/tag/.*", ".*/page/[0-9]+"]`
Control how many "clicks away" from the starting page the crawler can go.
* Min: `1`
* Max: `20`
* Default: `5`
Treat URLs with different query parameters as the same page, preventing duplicate crawls.
Get notified when your crawl completes or as pages are discovered.
**Configuration:**
* `url` (required) - String | Webhook URL to receive notifications
* `headers` - Object | Custom headers for authentication
* `metadata` - Object | Extra data to include in payloads
* `events` - Array | Which events trigger notifications: `started`, `page`, `completed`, `failed`
**Example:**
```json theme={"system"}
{
"callback":{
"url":"",
"headers":{},
"metadata":{},
"events":["started", "completed"]
}
}
```
Crawl the site as if you're browsing from a specific country.
Use ISO Alpha-2 country codes like `US`, `GB`, `FR`, `DE`, `CA`, `JP`, etc. Use `ALL` for random country selection.
Set the language preference for crawling. Use LCID standard.
**Locale Examples:**
* `en-US` - English (United States)
* `en-GB` - English (United Kingdom)
* `fr-FR` - French (France)
* `de-DE` - German (Germany)
## Usage
### Basic crawl
Crawl a website using default settings:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(url="https://www.nimbleway.com")
print(f"Crawl started with ID: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
});
console.log(`Crawl started with ID: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com"
}'
```
### Filter with URL patterns
Use include and exclude patterns to control which URLs are crawled:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
include_paths=["/blog/.*", "/use-cases/.*"],
exclude_paths=[".*/careers/.*"],
limit=500
)
print(f"Crawl ID: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
include_paths: ["/blog/.*", "/use-cases/.*"],
exclude_paths: [".*/careers/.*"],
limit: 500,
});
console.log(`Crawl ID: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"include_paths": ["/blog/.*", "/use-cases/.*"],
"exclude_paths": [".*/careers/.*"],
"limit": 500
}'
```
### Crawl entire domain
Allow crawler to follow all internal links beyond the starting path:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com/blog",
crawl_entire_domain=True,
limit=2000
)
print(f"Crawl ID: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com/blog",
crawl_entire_domain: true,
limit: 2000,
});
console.log(`Crawl ID: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com/blog",
"crawl_entire_domain": true,
"limit": 2000
}'
```
### Crawl with extraction
Extract structured data from each page during the crawl:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
limit=500,
extract_options={
"driver": "vx10",
"parse": True,
"formats": ["html", "markdown"]
}
)
print(f"Crawl ID: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
limit: 500,
extract_options: {
driver: "vx10",
parse: true,
formats: ["html", "markdown"],
},
});
console.log(`Crawl ID: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"limit": 500,
"extract_options": {
"driver": "vx10",
"parse": true,
"formats": ["html", "markdown"]
}
}'
```
### Combined parameters
Crawl with multiple parameters for precise control:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.crawl.run(
url="https://www.nimbleway.com",
name="Nimble Website Crawl",
sitemap="include",
allow_subdomains=True,
include_paths=["/use-cases/.*"],
limit=1000,
callback={
"url": "https://your-server.com/webhook",
"events": ["completed"]
}
)
print(f"Crawl started: {result.crawl_id}")
print(f"Status: {result.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.crawl.run({
url: "https://www.nimbleway.com",
name: "Nimble Website Crawl",
sitemap: "include",
allow_subdomains: true,
include_paths: ["/use-cases/.*"],
limit: 1000,
callback: {
url: "https://your-server.com/webhook",
events: ["completed"],
},
});
console.log(`Crawl started: ${result.crawl_id}`);
console.log(`Status: ${result.status}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/crawl' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"name": "Nimble Website Crawl",
"sitemap": "include",
"allow_subdomains": true,
"include_paths": ["/use-cases/.*"],
"limit": 1000,
"callback": {
"url": "https://your-server.com/webhook",
"events": ["completed"]
}
}'
```
## Managing Crawls
Get all your crawls filtered by status using the REST API:
* **Available status filters:** `pending`, `in_progress`, `completed`, `failed`, `canceled`
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# List crawls by status
my_crawls = nimble.crawl.list()
for crawl in my_crawls.data:
print(f"Crawl {crawl.crawl_id}: {crawl.name} - {crawl.status}")
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// List crawls by status
const myCrawls = await nimble.crawl.list();
myCrawls.data.forEach(crawl => {
console.log(`Crawl ${crawl.crawl_id}: ${crawl.name} - ${crawl.status}`);
});
```
```bash cURL theme={"system"}
# Filter by status: pending, in_progress, completed, failed, canceled
curl -X GET 'https://sdk.nimbleway.com/v2/crawl?status=completed' \
--header 'Authorization: Bearer '
```
Check progress and get the list of task IDs for a specific crawl using the REST API:
```python Python theme={"system"}
my_crawl = nimble.crawl.status(crawl_id)
print(f"Status: {my_crawl.status}")
```
```typescript TypeScript theme={"system"}
const myCrawl = await nimble.crawl.status(crawlId);
console.log(`Status: ${myCrawl.status}`);
```
```bash cURL theme={"system"}
curl -X GET 'https://sdk.nimbleway.com/v2/crawl/e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f' \
--header 'Authorization: Bearer '
```
Use the `task_id` from the crawl status response to fetch extracted content for each page using the REST API:
```python Python theme={"system"}
# my_crawl["tasks"] from step #2 contains list of task IDs from status response
for task in my_crawl.tasks:
if task.status == "completed":
task_result = nimble.tasks.get(task_id)
print(f"URL: {task_result['url']}")
print(f"HTML length: {len(task_result['data'].get('html', ''))}")
```
```typescript TypeScript theme={"system"}
// myCrawl.tasks from step #2 contains list of task IDs from status response
for (const task of myCrawl.tasks) {
if (task.status === "completed") {
const taskResponse = await nimble.tasks.get(taskId);
console.log(`URL: ${taskResult.url}`);
console.log(`HTML length: ${taskResult.data?.html?.length || 0}`);
}
}
```
```bash cURL theme={"system"}
# Get results for each task_id from the crawl status response
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR-API-KEY'
```
```json theme={"system"}
{
"url": "https://www.nimbleway.com/blog/post",
"task_id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"status": "success",
"task": {
"id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"state": "success",
"created_at": "2026-02-09T23:15:43.549Z",
"modified_at": "2026-02-09T23:16:39.094Z",
"account_name": "your-account"
},
"data": {
"html": "...",
"markdown": "# Page Title\n\nContent...",
"headers": { ... }
},
"metadata": {
"query_time": "2026-02-09T23:15:43.549Z",
"query_duration": 1877,
"response_parameters": {
"input_url": "https://www.nimbleway.com/blog/post"
},
"driver": "vx6"
},
"status_code": 200
}
```
Stop a running crawl using the REST API. Completed tasks remain available for result retrieval:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
response = nimble.crawl.terminate(crawl_id)
print(response) # {"status": "canceled"}
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const response = await nimble.crawl.terminate("e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f");
console.log(response); // { status: "canceled" }
```
```bash cURL theme={"system"}
curl -X DELETE 'https://sdk.nimbleway.com/v2/crawl/e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f' \
--header 'Authorization: Bearer '
```
## Response Fields
When you use **Crawl**, you receive:
* **Async operation** - Crawl jobs run in the background, check status or receive webhooks
* **Progress tracking** - Monitor `total`, `pending`, `completed`, and `failed` counts
* **Task-based results** - Each page becomes a task with extractable content
* **Webhook support** - Get notified in real-time as pages are processed
### Create Crawl Response
Returns immediate response with crawl job details
```json theme={"system"}
{
"crawl_id": "e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f",
"name": null,
"url": "https://www.nimbleway.com",
"status": "queued",
"account_name": "your-account",
"created_at": "2026-02-09T23:15:40.785Z",
"updated_at": "2026-02-09T23:15:40.785Z",
"completed_at": null,
"crawl_options": {
"sitemap": "include",
"crawl_entire_domain": false,
"limit": 10,
"max_discovery_depth": 5,
"ignore_query_parameters": false,
"allow_external_links": false,
"allow_subdomains": false
},
"extract_options": null
}
```
| Field | Type | Description |
| - | - | - |
| `crawl_id` | string | Unique identifier for the crawl job |
| `name` | string | Optional name you assigned to the crawl |
| `url` | string | Starting URL for the crawl |
| `status` | string | `queued`, `running`, `succeeded`, `failed`, `canceled` |
| `account_name` | string | Your account identifier |
| `created_at` | string | Timestamp when crawl was created |
| `updated_at` | string | Timestamp of last status update |
| `completed_at` | string | Timestamp when crawl completed (null if in progress) |
| `crawl_options` | object | Configuration settings applied to this crawl |
| `extract_options` | object | Extraction settings (null if not configured) |
### Get Crawl Status by ID Response
Returns the crawl object wrapped in a `crawl` key, with progress counters and task list:
```json theme={"system"}
{
"crawl": {
"crawl_id": "e3ca2ff1-b82a-472b-b1a9-ef4d29cc549f",
"name": null,
"url": "https://www.nimbleway.com",
"status": "succeeded",
"account_name": "your-account",
"total": 10,
"pending": 0,
"completed": 10,
"failed": 0,
"created_at": "2026-02-09T23:15:40.785Z",
"updated_at": "2026-02-09T23:17:08.083Z",
"completed_at": "2026-02-09T23:17:08.079Z",
"crawl_options": {
"sitemap": "include",
"crawl_entire_domain": false,
"limit": 10,
"max_discovery_depth": 5
},
"extract_options": null,
"tasks": [
{
"task_id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"status": "completed",
"updated_at": "2026-02-09T23:16:39.094Z",
"created_at": "2026-02-09T23:15:43.549Z"
},
{
"task_id": "3f6c136c-4bb5-44af-a21b-c8f1db708c2f",
"status": "completed",
"updated_at": "2026-02-09T23:16:45.033Z",
"created_at": "2026-02-09T23:15:42.966Z"
}
]
}
}
```
| Field | Type | Description |
| - | - | - |
| `crawl.crawl_id` | string | Unique identifier for the crawl job |
| `crawl.status` | string | Current crawl status |
| `crawl.total` | integer | Total URLs discovered |
| `crawl.pending` | integer | URLs waiting to be processed |
| `crawl.completed` | integer | Successfully processed URLs |
| `crawl.failed` | integer | Failed URL extractions |
| `crawl.tasks` | array | List of individual page tasks |
| `crawl.tasks[].task_id` | string | Task ID to use with `GET /v2/tasks/{id}/results` |
| `crawl.tasks[].status` | string | `pending`, `processing`, `completed`, `failed` |
## SDK and API methods
| Method | Availability | Description |
| :- | :- | :- |
| `nimble.crawl.run(url=..., ...)` | Python SDK | Create a new crawl job |
| `GET /v2/crawl` | REST API | List all crawls with pagination |
| `GET /v2/crawl/{crawl_id}` | REST API | Get crawl status and task list |
| `DELETE /v2/crawl/{crawl_id}` | REST API | Stop a running crawl |
| `GET /v2/tasks/{task_id}/results` | REST API | Get extracted content for a page |
The Python SDK currently supports creating crawl jobs via
`nimble.crawl.run()`. For crawl management operations (listing, status,
cancellation) and retrieving task results, use the REST API directly as shown
in the examples above.
## Use cases
Extract data from hundreds or thousands of pages across an entire website
Gather all product information from e-commerce sites automatically
Create complete snapshots of websites for analysis or backup
Track pricing across entire catalogs over time
### Real-world examples
**Scenario:** You need to gather all product information from a competitor's online store.
**How Crawl helps:**
* Discovers all product pages through sitemaps and navigation
* Extracts product details, prices, and descriptions from each page
* Handles pagination and category structures automatically
* Filters out cart, checkout, and account pages
**Result:** Complete product catalog data without manual URL collection.
**Scenario:** You're migrating a blog to a new platform and need all content.
**How Crawl helps:**
* Finds all blog posts through sitemap and internal links
* Extracts post content, metadata, and images
* Excludes tag pages, author archives, and pagination
* Preserves URL structure for redirects
**Result:** Complete content export ready for migration.
**Scenario:** You want to create an offline backup of documentation.
**How Crawl helps:**
* Maps entire documentation structure
* Extracts content from all pages
* Maintains hierarchy and navigation structure
* Captures code examples and technical content
**Result:** Complete documentation archive for offline access.
**Scenario:** You need to track competitor pricing across their entire catalog.
**How Crawl helps:**
* Discovers all product pages automatically
* Extracts pricing information from each page
* Runs on schedule to track changes over time
* Handles dynamic pricing and regional variations
**Result:** Comprehensive price intelligence data.
**Scenario:** You're auditing a website's content for SEO optimization.
**How Crawl helps:**
* Discovers all indexable pages
* Extracts titles, meta descriptions, and headings
* Identifies orphaned pages and broken links
* Maps internal linking structure
**Result:** Complete SEO audit data for analysis.
## Crawl vs Map
| Need | Use |
| - | - |
| Extract content from pages | **Crawl** |
| Deep link following | **Crawl** |
| Complex filtering patterns | **Crawl** |
| Webhook notifications | **Crawl** |
| Quick URL discovery only | **Map** - completes in seconds |
| URL list with titles/descriptions | **Map** |
**Try Use Map first** - Discover URLs quickly with Map, then use Crawl to
extract content from the pages you need.
## Next steps
Explore endpoints, request parameters, and response schemas for the Crawl
API
# Domain Knowledge
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/domain-knowledge
Resolve the optimal driver and detect antibots for any URL or Extract Template
Look up which driver Nimble recommends for a given domain or Extract Template — including detected antibot systems and whether JavaScript rendering is required. Use the result to set the `driver` parameter explicitly on Extract or Extract Template requests.
Nimble selects the optimal driver automatically on every request. Use Domain
Knowledge when you want to understand the driver tier before running at scale,
or to pin a specific tier for cost predictability.
## Quick Start
### Example Request
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# By URL — or pass agent="target_serp" instead
result = nimble.domain_knowledge.get_driver(url="www.lowes.com")
print(result.model_dump())
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// By URL — or pass { agent: "target_serp" } instead
const result = await nimble.domainKnowledge.getDriver({ url: "www.lowes.com" });
console.log(result);
```
```go Go theme={"system"}
package main
import (
"context"
"fmt"
nimble "github.com/Nimbleway/nimble-go"
"github.com/Nimbleway/nimble-go/option"
"github.com/Nimbleway/nimble-go/packages/param"
)
func main() {
client := nimble.NewClient(option.WithAPIKey("YOUR-API-KEY"))
// By URL — or set Agent instead
result, err := client.DomainKnowledge.GetDriver(context.Background(), nimble.DomainKnowledgeGetDriverParams{
URL: param.NewOpt("www.lowes.com"),
})
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", result)
}
```
```bash CLI theme={"system"}
# By URL — or pass --agent target_serp instead
nimble domain-knowledge get-driver --url www.lowes.com
```
```bash cURL theme={"system"}
curl -X GET 'https://sdk.nimbleway.com/v2/domain-knowledge/driver?url=www.lowes.com' \
--header 'Authorization: Bearer '
```
### Example Response
```json theme={"system"}
{
"url": "www.lowes.com",
"driver": "vx6",
"antibots": ["akamai", "threatmetrix", "recaptcha", "akamai-sbsd"],
"description": "Native Driver - non rendered fast request",
"need_to_render": false
}
```
Agent lookup response:
```json theme={"system"}
{
"agent": "target_serp",
"driver": "wsa-10m",
"antibots": ["perimeterx", "f5-shape", "recaptcha"],
"description": "Stealth Driver - stealth browser with advanced bypassing any site, for Agents managed by Nimble",
"need_to_render": true
}
```
## How it works
Pass either a `url` (domain) or an Extract Template name in the `agent` query parameter. Exactly
one must be provided.
Nimble queries its domain knowledge database to identify the antibot systems
protecting the target and returns the recommended driver.
Use the returned `driver` value in the `driver` parameter of an Extract or
Extract Template request to skip auto-detection and use the resolved tier directly.
## Parameters
Target domain to resolve the driver for.
**Example:** `amazon.com`
Mutually exclusive with `agent` — provide exactly one.
Extract Template name to resolve the driver for.
**Example:** `amazon_pdp`
Mutually exclusive with `url` — provide exactly one.
## Response fields
| Field | Type | Description |
| - | - | - |
| `driver` | string | Recommended driver (e.g. `vx6`, `vx8`, `vx10`, `wsa-6m`) |
| `description` | string | Human-readable description of the driver |
| `antibots` | array | Antibot systems detected on the domain (e.g. `cloudflare`, `perimeterx`, `recaptcha`) |
| `need_to_render` | boolean | Whether JavaScript rendering is required |
| `url` | string | Echoed input (present when `url` query param was used) |
| `agent` | string | Echoed input (present when `agent` query param was used) |
## Using the result with Extract
Pass the resolved `driver` value directly to an Extract request to skip auto-detection.
If the resolved driver is a `wsa-*` value, use the equivalent `vx` driver
instead when calling Extract. See the conversion table below.
| Template driver | Extract driver |
| - | - |
| `wsa-6` / `wsa-6m` | `vx6` |
| `wsa-8` / `wsa-8m` | `vx8` |
| `wsa-10` / `wsa-10m` | `vx10` |
```bash cURL theme={"system"}
# Step 1 — resolve the driver
curl -s -X GET 'https://sdk.nimbleway.com/v2/domain-knowledge/driver?url=www.lowes.com' \
--header 'Authorization: Bearer '
# Step 2 — use the resolved driver in Extract
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.lowes.com/pl/Drills/4294857312",
"driver": ""
}'
```
## Related
Use the resolved driver in Extract requests via the `driver` parameter
Resolve the driver for any template
Understand how driver tiers affect request cost
# Advanced Options
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/advanced-options
Fine-tune extract requests with referrer policies, session control, device emulation, and more
Advanced options let you control how Nimble issues requests at a lower level. Use them to tune referrer behavior, accept specific response codes, switch protocols, persist sessions, tag requests, or emulate different device types.
## When to use
Use advanced options when you need to:
* **Control referrer behavior**: Set realistic referrers to bypass referrer-based gating
* **Accept non-200 responses**: Treat specific status codes as successful
* **Switch to HTTP/2**: Required by some modern sites for correct responses
* **Persist sessions**: Reuse the same browser session across requests
* **Tag requests**: Attach a label for tracking or analytics
* **Emulate devices**: Render pages as mobile or tablet instead of desktop
These are advanced parameters. Misconfiguring them can cause requests to fail
or trigger anti-bot detection.
## Parameters
Referrer policy for the request. Nimble sends the chosen referrer header with the request to mimic real browsing behavior.
**Generic policies:**
* `random` - Use a randomly selected referrer
* `no-referrer` - Do not send any referrer
* `same-origin` - Use the target site's own origin as referrer
**Search and social referrers:**
* `google`, `bing` - Search engine referrers
* `facebook`, `twitter`, `instagram` - Social platform referrers
**Example:**
```json theme={"system"}
"referrer_type": "google"
```
HTTP status codes that should be treated as successful. By default, only `200` is considered success. Use this to accept responses like `201` (Created), `204` (No Content), or `302` (Redirect).
**Format:** Array of integers.
**Example:**
```json theme={"system"}
"expected_status_codes": [200, 201, 204]
```
Use HTTP/2 instead of HTTP/1.1 for the request. Some sites require HTTP/2 to return the expected response.
**Example:**
```json theme={"system"}
"http2": true
```
Persist a browser session across multiple requests. Useful for maintaining login state, cookies, or navigation context between calls.
**Fields:**
* `id` (string) - Session identifier. Reuse the same ID across requests to share the session.
* `timeout` (number) - Session timeout in seconds. Must be greater than 0.
* `retry` (boolean, default `false`) - Retry the request within the same session on failure.
* `prefetch_userbrowser` (boolean, default `false`) - Preload the user browser for faster subsequent requests.
**Example:**
```json theme={"system"}
"session": {
"id": "my-session-123",
"timeout": 300,
"retry": true,
"prefetch_userbrowser": true
}
```
User-defined label attached to the request. Use tags to group requests by campaign, workflow, or customer for tracking and analytics.
**Example:**
```json theme={"system"}
"tag": "campaign-2026-q2"
```
Maximum time in milliseconds to wait for the request to complete. Increase for slow or JS-heavy pages; decrease to fail fast on unresponsive targets.
**Example:**
```json theme={"system"}
"request_timeout": 60000
```
Device type for browser emulation. Affects viewport size, user agent, and rendering behavior.
**Options:** `desktop` | `mobile` | `tablet`
**Example:**
```json theme={"system"}
"device": "mobile"
```
## Usage
### Set a referrer
Send a Google referrer to mimic search engine traffic.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
referrer_type="google"
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
referrer_type: "google",
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"referrer_type": "google"
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--referrer-type google
```
### Accept multiple status codes
Treat `200`, `201`, and `204` all as successful responses.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://api.example.com/created",
expected_status_codes=[200, 201, 204]
)
print(result.status_code)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://api.example.com/created",
expected_status_codes: [200, 201, 204],
});
console.log(result.status_code);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://api.example.com/created",
"expected_status_codes": [200, 201, 204]
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://api.example.com/created" \
--expected-status-code 200 --expected-status-code 201 --expected-status-code 204
```
### Force HTTP/2
Force the request to use HTTP/2 instead of HTTP/1.1.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
http2=True
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
http2: true,
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"http2": true
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--http2 true
```
### Persist a session
Reuse a browser session across multiple requests.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com/product/123",
session={
"id": "shopping-session-abc",
"timeout": 600,
"retry": True
}
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com/product/123",
session: {
id: "shopping-session-abc",
timeout: 600,
retry: true,
},
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com/product/123",
"session": {
"id": "shopping-session-abc",
"timeout": 600,
"retry": true
}
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com/product/123" \
--session '{id: shopping-session-abc, timeout: 600, retry: true}'
```
### Tag a request
Attach a label to the request for tracking.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
tag="campaign-2026-q2"
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
tag: "campaign-2026-q2",
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"tag": "campaign-2026-q2"
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--tag "campaign-2026-q2"
```
### Set a wait strategy
Control when Nimble considers the page ready with a `wait_for_navigation` [browser action](/nimble-sdk/web-tools/extract/features/browser-actions):
| Value | When to use |
| - | - |
| `load` **(default)** | Waits for the load event — HTML, CSS, images, and all subresources have finished loading. |
| `domcontentloaded` | Fires once the HTML is parsed and the DOM is built, without waiting for images or stylesheets. Fastest option. |
| `networkidle0` | Waits until there are 0 in-flight network requests for at least 500ms. Most thorough, but slowest. |
| `networkidle2` | Waits until there are 2 or fewer in-flight network requests for at least 500ms. Good for pages with lazy-loaded content. |
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
browser_actions=[{"wait_for_navigation": "networkidle2"}]
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [{ wait_for_navigation: "networkidle2" }],
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{"url": "https://www.example.com", "render": true, "browser_actions": [{"wait_for_navigation": "networkidle2"}]}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--render true \
--browser-action '{wait_for_navigation: "networkidle2"}'
```
### Limit request time
Cap how long Nimble waits for the request with `request_timeout` (milliseconds):
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
request_timeout=60000
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
request_timeout: 60000,
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{"url": "https://www.example.com", "request_timeout": 60000}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--request-timeout 60000
```
### Emulate a device
Render the page as a mobile device.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
device="mobile"
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
device: "mobile",
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"device": "mobile"
}'
```
```bash CLI theme={"system"}
nimble extract run \
--url "https://www.example.com" \
--device mobile
```
## Related
Send custom request headers and cookies
Route requests through specific countries, states, or cities
Bypass anti-bot detection with real user emulation
Render JavaScript-heavy pages before extraction
# Async & Batch
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/async
Run extraction tasks asynchronously or submit multiple URLs in a single batch request
Extract data in the background with async requests, or submit multiple URLs at once with batch. Both modes return immediately without blocking your application while Nimble processes the work.
## Quick Start
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR_API_KEY")
response = nimble.extract.async_(
url="https://www.example.com",
render=True,
formats=["markdown"]
)
print(f"Task created: {response.task.id}")
# Poll GET /v2/tasks/{task_id} until state == "success"
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR_API_KEY" });
const response = await nimble.extract.async({
url: "https://www.example.com",
render: true,
formats: ["markdown"],
});
console.log(`Task created: ${response.task.id}`);
// Poll GET /v2/tasks/{taskId} until state === "success"
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"formats": ["markdown"]
}'
```
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR_API_KEY")
response = nimble.extract.batch(
inputs=[
{"url": "https://www.example.com/page1"},
{"url": "https://www.example.com/page2"},
{"url": "https://www.example.com/page3"},
],
shared_inputs={
"render": True,
"formats": ["markdown"],
"storage_type": "s3",
"storage_url": "s3://my-bucket/results/",
}
)
print(f"Batch created: {response.batch_id} ({response.batch_size} tasks)")
# Poll GET /v2/batches/{batch_id}/progress until completed == true
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR_API_KEY" });
const response = await nimble.extract.batch({
inputs: [
{ url: "https://www.example.com/page1" },
{ url: "https://www.example.com/page2" },
{ url: "https://www.example.com/page3" },
],
shared_inputs: {
render: true,
formats: ["markdown"],
storage_type: "s3",
storage_url: "s3://my-bucket/results/",
},
});
console.log(`Batch created: ${response.batch_id} (${response.batch_size} tasks)`);
// Poll GET /v2/batches/{batchId}/progress until completed === true
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/batch' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": [
{"url": "https://www.example.com/page1"},
{"url": "https://www.example.com/page2"},
{"url": "https://www.example.com/page3"}
],
"shared_inputs": {
"render": true,
"formats": ["markdown"],
"storage_type": "s3",
"storage_url": "s3://my-bucket/results/"
}
}'
```
Use async or batch when you need to:
* Run extractions without blocking your application
* Process large volumes of URLs efficiently
* Deliver results to cloud storage (S3 / GCS) automatically
* Receive webhook notifications when tasks complete
* Integrate extraction into scheduled or queued workflows
## How it works
Send a POST request to the async or batch endpoint. The API returns
immediately with a `task_id` (async) or `batch_id` (batch) — no waiting for
extraction to finish.
Extraction runs asynchronously. For batch, each URL becomes an independent
task processed in parallel.
Choose how results are delivered:
* **Polling** — check task status and fetch results on demand
* **Webhook** — get notified automatically when a task completes
* **Cloud storage** — results saved directly to your S3 or GCS bucket
For setup instructions, code examples, and bucket permission configuration,
see the [Callbacks & Delivery](/nimble-sdk/admin/callbacks-and-delivery)
guide.
***
## Async
Submit a single URL and receive a `task_id` immediately. Nimble processes the extraction in the background — retrieve results via polling, webhook, or cloud storage.
```bash theme={"system"}
POST https://sdk.nimbleway.com/v2/extract/async
```
### Parameters
Accepts all parameters from the [Extract API](/nimble-sdk/web-tools/extract/quickstart), plus async-specific delivery options:
* `url` (required) — The webpage to extract
* `formats` — Output formats: `html`, `markdown`, `text`, `screenshot`
* `render` — Enable JavaScript rendering
* `driver` — Extraction engine: `vx6`, `vx8`, `vx10`, etc.
* `country`, `state`, `city` — Geo-targeting
* `parse`, `parser` — Structured data extraction
* `browser_actions`, `network_capture` — Advanced interactions
**Async-specific parameters:**
Storage provider for results. When specified, results are saved to your cloud storage instead of Nimble's servers.
**Options:** `s3` (Amazon S3), `gs` (Google Cloud Storage)
Bucket path where results will be saved. Results are stored as `{task_id}.json` at the specified location.
**Format:** `s3://your-bucket/path/prefix/`
Compress results with GZIP before saving. When `true`, results are saved as `{task_id}.json.gz`.
Custom filename for the stored object instead of the default task ID.
**Example:** `"my-custom-name"` saves as `my-custom-name.json`
Webhook URL to receive a POST request when the task completes. Nimble sends task metadata (without result data) to this URL when extraction finishes.
**Example:** `https://your-api.com/webhook/complete`
### Examples
Submit a URL and receive a `task_id` immediately. All three delivery methods below return the same initial response — the difference is how you retrieve results once the task completes.
Poll the task endpoint to check status and retrieve results when complete.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR_API_KEY")
response = nimble.extract.async_(
url="https://www.example.com",
render=True,
formats=["html", "markdown"]
)
task_id = response.task.id
print(f"Task created: {task_id}")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR_API_KEY" });
const response = await nimble.extract.async({
url: "https://www.example.com",
render: true,
formats: ["html", "markdown"],
});
const taskId = response.task.id;
console.log(`Task created: ${taskId}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"formats": ["html", "markdown"]
}'
```
Results are saved automatically to your bucket as the task completes. No need to poll — the file appears at `storage_url/storage_object_name.json.gz` when done.
```python Python theme={"system"}
response = nimble.extract.async_(
url="https://www.example.com",
render=True,
formats=["html", "markdown"],
storage_type="s3",
storage_url="s3://my-bucket/nimble-extracts/",
storage_compress=True,
storage_object_name="example-com-extraction"
)
print(f"Task created: {response.task.id}")
print(f"Results will be saved to: s3://my-bucket/nimble-extracts/example-com-extraction.json.gz")
```
```typescript TypeScript theme={"system"}
const response = await nimble.extract.async({
url: "https://www.example.com",
render: true,
formats: ["html", "markdown"],
storage_type: "s3",
storage_url: "s3://my-bucket/nimble-extracts/",
storage_compress: true,
storage_object_name: "example-com-extraction",
});
console.log(`Task created: ${response.task.id}`);
console.log("Results will be saved to: s3://my-bucket/nimble-extracts/example-com-extraction.json.gz");
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"formats": ["html", "markdown"],
"storage_type": "s3",
"storage_url": "s3://my-bucket/nimble-extracts/",
"storage_compress": true,
"storage_object_name": "example-com-extraction"
}'
```
Nimble sends a POST to your `callback_url` when the task completes. No polling required — your server receives the notification automatically.
```python Python theme={"system"}
response = nimble.extract.async_(
url="https://www.example.com",
render=True,
formats=["html"],
callback_url="https://your-api.com/webhooks/extract-complete"
)
print(f"Task created: {response.task.id}")
```
```typescript TypeScript theme={"system"}
const response = await nimble.extract.async({
url: "https://www.example.com",
render: true,
formats: ["html", "markdown"],
callback_url: "https://your-api.com/webhooks/extract-complete",
});
console.log(`Task created: ${response.task.id}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/async' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"formats": ["html", "markdown"],
"callback_url": "https://your-api.com/webhooks/extract-complete"
}'
```
Nimble POSTs task metadata to your URL when complete:
```json theme={"system"}
{
"status": "success",
"task": {
"id": "8e8cfde8-345b-42b8-b3e2-0c61eb11e00f",
"state": "pending",
"created_at": "2026-01-24T12:36:24.685Z",
"modified_at": "2026-01-24T12:36:24.685Z",
"input": {}
}
}
```
The endpoint returns immediately with a task ID:
```json theme={"system"}
{
"status": "success",
"task": {
"id": "8e8cfde8-345b-42b8-b3e2-0c61eb11e00f",
"state": "pending",
"created_at": "2026-01-24T12:36:24.685Z",
"modified_at": "2026-01-24T12:36:24.685Z",
"input": {}
}
}
```
### Status & Results
When **polling**, the typical flow is:
1. Poll `GET /v2/tasks/{task_id}` until `state: "success"`
2. Call `GET /v2/tasks/{task_id}/results` to retrieve the extracted data
**Task states:**
| State | Description |
| - | - |
| `pending` | Task queued, waiting to start |
| `success` | Extraction complete, results available |
| `error` | Extraction failed |
#### Retrieve results
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/tasks/{task_id}/results
```
```json theme={"system"}
{
"url": "https://www.nimbleway.com/blog/post",
"task_id": "ec89b1f7-1cf2-40eb-91b4-78716093f9ed",
"status": "success",
"data": {
"html": "...",
"markdown": "# Page Title\n\nContent..."
},
"metadata": {
"query_duration": 1877,
"driver": "vx6"
},
"status_code": 200
}
```
#### Check a task
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/tasks/{task_id}
```
```json theme={"system"}
{
"task": {
"id": "8e8cfde8-345b-42b8-b3e2-0c61eb11e00f",
"state": "success",
"status_code": 200,
"created_at": "2026-01-24T12:36:24.685Z",
"api_type": "extract"
}
}
```
#### List all tasks (paginated)
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/tasks?limit=100&cursor={cursor}
```
```json theme={"system"}
{
"data": [
{
"id": "8e8cfde8-...",
"state": "success",
"batch_id": "b7e1a2f3-...",
"api_type": "extract",
"created_at": "2026-03-19T10:00:00.000Z",
"download_url": "https://sdk.nimbleway.com/v2/tasks/8e8cfde8-.../results"
}
],
"pagination": {
"has_next": true,
"next_cursor": "eyJpZCI6Ij...",
"total": 142
}
}
```
***
## Batch
Submit up to **1,000 URLs in a single request**. Each URL runs as an independent async task. Use `shared_inputs` to apply common settings across all URLs — individual items in `inputs` can override any shared value.
```bash theme={"system"}
POST https://sdk.nimbleway.com/v2/extract/batch
```
### Parameters
Array of per-URL extraction requests. Supports up to **1,000 items** per batch. Each item accepts all [Core extraction parameters](/nimble-sdk/web-tools/extract/quickstart) — `url` is the only required field per item.
Per-item values override anything set in `shared_inputs`:
```json theme={"system"}
"inputs": [
{ "url": "https://example.com/us-page", "country": "US" },
{ "url": "https://example.com/il-page", "country": "IL" }
]
```
Default parameters applied to every item in the batch. Accepts two categories:
**Delivery params** (batch-wide, not overridable per item):
* `storage_type` — `s3` or `gs`
* `storage_url` — bucket path for results
* `storage_compress` — GZIP compress results
* `storage_object_name` — custom filename prefix
* `callback_url` — webhook on completion
**Extraction defaults** (applied to all items, overridable per item in `inputs`):
* `render`, `driver`, `formats`, `country`, `locale`, `parse`, `parser`, and others
### Examples
Parameters set in `shared_inputs` are applied as defaults to all items in `inputs`. Any value set inside an individual item overrides the shared default.
Extract several unique URLs with results delivered to S3 and a webhook callback on completion:
```python Python theme={"system"}
response = nimble.extract.batch(
inputs=[
{"url": "https://www.finance.com"},
{"url": "https://www.travel.com"},
{"url": "https://www.socialmedia.com"},
],
shared_inputs={
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete",
}
)
print(f"Batch ID: {response.batch_id}")
print(f"Tasks submitted: {response.batch_size}")
```
```typescript TypeScript theme={"system"}
const response = await nimble.extract.batch({
inputs: [
{ url: "https://www.finance.com" },
{ url: "https://www.travel.com" },
{ url: "https://www.socialmedia.com" },
],
shared_inputs: {
storage_type: "s3",
storage_url: "s3://your-bucket/results/",
callback_url: "https://your-api.com/webhooks/batch-complete",
},
});
console.log(`Batch ID: ${response.batch_id}`);
console.log(`Tasks submitted: ${response.batch_size}`);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/batch' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": [
{"url": "https://www.finance.com"},
{"url": "https://www.travel.com"},
{"url": "https://www.socialmedia.com"}
],
"shared_inputs": {
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete"
}
}'
```
Set a different country per URL. Items without a country fall back to the shared default (`CA`):
```python Python theme={"system"}
response = nimble.extract.batch(
inputs=[
{"url": "https://www.finance.com", "country": "US", "locale": "en-US"},
{"url": "https://www.travel.com", "country": "FR", "locale": "fr-FR"},
{"url": "https://www.socialmedia.com", "country": "DE", "locale": "de-DE"},
{"url": "https://www.searchengine.com"}, # falls back to shared country: CA
],
shared_inputs={
"country": "CA",
"locale": "en-CA",
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete",
}
)
```
```typescript TypeScript theme={"system"}
const response = await nimble.extract.batch({
inputs: [
{ url: "https://www.finance.com", country: "US", locale: "en-US" },
{ url: "https://www.travel.com", country: "FR", locale: "fr-FR" },
{ url: "https://www.socialmedia.com", country: "DE", locale: "de-DE" },
{ url: "https://www.searchengine.com" }, // falls back to shared country: CA
],
shared_inputs: {
country: "CA",
locale: "en-CA",
storage_type: "s3",
storage_url: "s3://your-bucket/results/",
callback_url: "https://your-api.com/webhooks/batch-complete",
},
});
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/batch' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": [
{"url": "https://www.finance.com", "country": "US", "locale": "en-US"},
{"url": "https://www.travel.com", "country": "FR", "locale": "fr-FR"},
{"url": "https://www.socialmedia.com", "country": "DE", "locale": "de-DE"},
{"url": "https://www.searchengine.com"}
],
"shared_inputs": {
"country": "CA",
"locale": "en-CA",
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete"
}
}'
```
Set the URL once in `shared_inputs` and vary only the country per item — useful for geo-comparison:
```python Python theme={"system"}
response = nimble.extract.batch(
inputs=[
{"country": "US", "locale": "en-US"},
{"country": "FR", "locale": "fr-FR"},
{"country": "DE", "locale": "de-DE"},
],
shared_inputs={
"url": "https://www.finance.com",
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete",
}
)
```
```typescript TypeScript theme={"system"}
const response = await nimble.extract.batch({
inputs: [
{ country: "US", locale: "en-US" },
{ country: "FR", locale: "fr-FR" },
{ country: "DE", locale: "de-DE" },
],
shared_inputs: {
url: "https://www.finance.com",
storage_type: "s3",
storage_url: "s3://your-bucket/results/",
callback_url: "https://your-api.com/webhooks/batch-complete",
},
});
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract/batch' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": [
{"country": "US", "locale": "en-US"},
{"country": "FR", "locale": "fr-FR"},
{"country": "DE", "locale": "de-DE"}
],
"shared_inputs": {
"url": "https://www.finance.com",
"storage_type": "s3",
"storage_url": "s3://your-bucket/results/",
"callback_url": "https://your-api.com/webhooks/batch-complete"
}
}'
```
The endpoint returns immediately with a `batch_id` and the initial task list:
```json theme={"system"}
{
"batch_id": "b7e1a2f3-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"batch_size": 3,
"tasks": [
{ "id": "task-001-uuid", "state": "pending", "batch_id": "b7e1a2f3-..." },
{ "id": "task-002-uuid", "state": "pending", "batch_id": "b7e1a2f3-..." }
]
}
```
### Status & Results
When **polling**, the typical flow is:
1. Poll `/v2/batches/{batch_id}/progress` until `completed: true`
2. Fetch `/v2/batches/{batch_id}` to get all task IDs and states
3. For each `success` task, call `GET /v2/tasks/{task_id}/results`
**Batch states:**
| State | Description |
| - | - |
| `pending` | Task queued, waiting to start |
| `in_progress` | Task is currently being processed |
| `success` | Extraction complete, results available |
| `error` | Extraction failed |
#### Poll for batch completion
Call `/v2/batches/{batch_id}/progress` repeatedly until `completed: true`. This is a lightweight endpoint — use it for polling.
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/batches/{batch_id}/progress
```
```json theme={"system"}
{
"id": "b7e1a2f3-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"completed": false,
"completed_count": 47,
"progress": 0.47,
"completed_at": null
}
```
#### Fetch the full batch details
Once `completed: true`, fetch the batch details to get all task IDs, states, and download URLs.
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/batches/{batch_id}
```
```json theme={"system"}
{
"id": "b7e1a2f3-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"completed": true,
"completed_count": 3,
"progress": 1.0,
"tasks": [
{
"id": "task-001-uuid",
"state": "success",
"download_url": "https://sdk.nimbleway.com/v2/tasks/task-001-uuid/results"
},
{
"id": "task-002-uuid",
"state": "error",
"error": "Connection timeout"
}
]
}
```
#### Retrieve results per task
Iterate over the task list and call `GET /v2/tasks/{task_id}/results` for each `success` task.
```python Python theme={"system"}
import requests
batch = requests.get(
f"https://sdk.nimbleway.com/v2/batches/{batch_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"}
).json()
for task in batch["tasks"]:
if task["state"] == "success":
result = requests.get(
task["download_url"],
headers={"Authorization": "Bearer YOUR_API_KEY"}
).json()
print(result["data"]["markdown"])
```
```typescript TypeScript theme={"system"}
const batch = await fetch(`https://sdk.nimbleway.com/v2/batches/${batchId}`, {
headers: { Authorization: "Bearer YOUR_API_KEY" },
}).then((r) => r.json());
for (const task of batch.tasks) {
if (task.state === "success") {
const result = await fetch(task.download_url, {
headers: { Authorization: "Bearer YOUR_API_KEY" },
}).then((r) => r.json());
console.log(result.data.markdown);
}
}
```
```bash cURL theme={"system"}
# Fetch results for a single task (repeat per task_id)
curl 'https://sdk.nimbleway.com/v2/tasks/{task_id}/results' \
--header 'Authorization: Bearer YOUR_API_KEY'
```
#### List all batches
```bash theme={"system"}
GET https://sdk.nimbleway.com/v2/batches
```
```json theme={"system"}
{
"data": [
{
"id": "b7e1a2f3-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"completed": true,
"created_at": "2026-03-19T10:00:00.000Z",
"tasks": ["task-001-uuid", "task-002-uuid", "task-003-uuid"]
}
],
"pagination": { "has_next": false, "next_cursor": null, "total": 1 }
}
```
***
## Data Retention
Results are retained for **7 days**. For longer retention, use cloud storage (`storage_url`) to persist results indefinitely.
| Item | Expiration |
| - | - |
| Pending tasks | 24 hours if not started |
| Completed results | 24–48 hours (indefinite with cloud storage) |
| Failed tasks | 24 hours |
## API Reference
Check the status of a single async task
Retrieve the full task list and states for a batch
# Browser Actions
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/browser-actions
Full control over browser actions with predefined steps
**Browser Actions** gives you full control over the sequence of actions performed in the browser, ensuring predictable and repeatable results. You manually specify each step like click, scroll, wait, or type.
Common uses:
* **Load more content**: Click "Show more" buttons or scroll to reveal additional items.
* **Set filters**: Select dropdowns, checkboxes, or date ranges to refine displayed data.
* **Submit searches**: Enter search terms and submit forms to access results pages.
* **Trigger lazy-loaded data**: Scroll to load images, products, or infinite feeds.
## When to use
Use Browser Actions when you need:
* **Predictable execution**: Same steps run every time
* **Full control**: Specify exact actions and sequences
* **Fast performance**: No AI inference overhead
Browser Action flows may break when page structure or selectors change.
Monitor and update your flows as needed.
## Parameters
**`Must be set to true`** to use browser actions. This enables the browser to execute JavaScript and perform interactions.
Browser actions only work when the page is rendered in a real browser environment.
**Example:**
```json theme={"system"}
"render": true
```
A list of actions to perform in sequence on the page. Each action is an object specifying what to do (click, scroll, wait, type, etc.).
**Actions execute in order** - the second action waits for the first to complete, and so on.
**Global timeout:** All actions must complete within 240 seconds total.
**Available actions:**
* `goto` - Navigate to a different URL
* `wait` - Pause for a specified duration
* `wait_for_element` - Wait for an element to appear
* `click` - Click an element
* `press` - Press a keyboard key (Enter, Tab, Escape, etc.)
* `fill` - Type or paste text into an input field
* `scroll` - Scroll the page by pixels or to an element
* `auto_scroll` - Automatically scroll to load lazy content
* `screenshot` - Capture a screenshot
* `get_cookies` - Extract cookies from the page
* `fetch` - Make HTTP requests from the browser context
Each action has specific parameters detailed in the sections below.
**Example:**
```json theme={"system"}
"browser_actions": [
{
"fill": {
"selector": "input[type='search']",
"value": "laptops"
}
},
{
"click": {
"selector": "button[type='submit']"
}
},
{
"wait": "2s"
}
]
```
All browser actions execute sequentially within a global **240-second
timeout**. If any action fails, the entire flow stops unless marked as
optional.
## Usage
Navigate to a different URL during the browser session.
**Parameters:**
**Direct form** - Pass URL as a string:
* `goto` (required) - URL to navigate to
**Extended form** - Use object with options:
* `url` (required) - URL to navigate to (string)
* `timeout` (optional) - Maximum wait time in milliseconds (number, default: 30000)
* `referer` (optional) - HTTP referer header to send (string)
* `wait_until` (optional) - Navigation wait condition: `'load'` (default), `'domcontentloaded'`, `'networkidle0'`, or `'networkidle2'`
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - simple navigation
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"goto": "https://www.example.com/products" # Navigate to products page
}
]
)
# Extended form - with wait condition
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"goto": {
"url": "https://www.example.com/products",
"wait_until": "networkidle2",
"timeout": 5000,
"referer": "https://www.google.com"
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - simple navigation
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
goto: "https://www.example.com/products" // Navigate to products page
}
]
});
// Extended form - with wait condition
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
goto: {
url: "https://www.example.com/products",
wait_until: "networkidle2",
timeout: 5000,
referer: "https://www.google.com"
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"goto": "https://www.example.com/products"
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"goto": {
"url": "https://www.example.com/products",
"wait_until": "networkidle2",
"timeout": 5000,
"referer": "https://www.google.com"
}
}]
}'
```
Add a delay between actions to simulate human behavior or allow elements to load.
**Parameters:**
**Direct form** - Pass duration as a string:
* `wait` (required) - Duration string (e.g., "2s", "500ms", "1.5s")
**Extended form** - Use object with options:
* `duration` (required) - Duration string (e.g., "2s", "500ms", "1.5s")
* `required` (optional) - Set to `false` to make this step optional (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - simple delay
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"wait": "2s" # Wait for 2 seconds
}
]
)
# Extended form - with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"wait": {
"duration": "500ms",
"required": False
}
}
]
)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - simple delay
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
wait: "2s" // Wait for 2 seconds
}
]
});
// Extended form - with options
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
wait: {
duration: "500ms",
required: false
}
}
]
});
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"wait": "2s"
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"wait": {
"duration": "500ms",
"required": false
}
}]
}'
```
Wait for DOM elements to appear on the page.
**Parameters:**
**Direct form** - Pass single selector or array of selectors:
* `wait_for_element` (required) - CSS selector string or array of selectors to wait for (any one matching triggers success)
**Extended form** - Use object with options:
* `selector` (required) - CSS selector string or array of selectors to wait for
* `timeout` (optional) - Maximum time in milliseconds to wait (number, default: 30000)
* `visible` (optional) - Whether element must be visible (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - wait for single element
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"wait_for_element": "#submit-button"
}
]
)
# Direct form - wait for multiple elements
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"wait_for_element": ["#element1", ".class2"]
}
]
)
# Extended form - with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"wait_for_element": {
"selector": "#element",
"timeout": 30000,
"visible": False
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - wait for single element
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
wait_for_element: "#submit-button"
}
]
});
// Direct form - wait for multiple elements
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
wait_for_element: ["#element1", ".class2"]
}
]
});
// Extended form - with options
const result3 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
wait_for_element: {
selector: "#element",
timeout: 30000,
visible: false
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form - single selector
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"wait_for_element": "#submit-button"
}]
}'
# Direct form - multiple selectors
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"wait_for_element": ["#element1", ".class2"]
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"wait_for_element": {
"selector": "#element",
"timeout": 30000,
"visible": false
}
}]
}'
```
Click on an element by selector, optionally at an offset within it.
**Parameters:**
**Direct form** - Pass CSS selector as string:
* `click` (required) - CSS selector string
**Extended form (selector mode)** - Use object with selector options:
* `selector` (required) - CSS selector of element to click (string or array of strings)
* `timeout` (optional) - Maximum time in milliseconds to wait for the element (number, default: 30000)
* `visible` (optional) - Whether element must be visible (boolean, default: `true`)
* `delay` (optional) - Delay before clicking (number in ms or Duration string, default: "0ms")
* `scroll` (optional) - Whether to scroll element into view (boolean, default: `true`)
* `count` (optional) - Number of times to click (number, default: 1)
* `offset_x` / `offset_y` (optional) - Click at an offset from the element's top-left corner (number)
* `steps` (optional) - Mouse movement steps for granular control (number, advanced)
* `strategy` (optional) - Mouse movement strategy for behavioral simulation (advanced)
* `required` (optional) - Set to `false` to make this step optional (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - simple click by selector
result = nimble.extract.run(
url="https://www.example.com",
render=True,
browser_actions=[
{
"click": "button.submit" # Click element matching selector
}
]
)
# Extended form (selector mode) - with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"click": {
"selector": "button.load-more",
"timeout": 20000,
"delay": "500ms",
"scroll": True,
"visible": True,
"count": 2 # Double-click
}
}
]
)
# Click at an offset from an element
result = nimble.extract.run(
url="https://www.example.com",
render=True,
browser_actions=[
{
"click": {
"selector": "#container",
"offset_x": 10,
"offset_y": 10 # Click 10px from top-left of container
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - simple click by selector
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
click: "button.submit" // Click element matching selector
}
]
});
// Extended form (selector mode) - with options
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
click: {
selector: "button.load-more",
timeout: 20000,
delay: "500ms",
scroll: true,
visible: true,
count: 2 // Double-click
}
}
]
});
// Click at an offset from an element
const result3 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
click: {
selector: "#container",
offset_x: 10,
offset_y: 10 // Click 10px from top-left of container
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form - simple click by selector
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"click": "button.submit"
}]
}'
# Extended form (selector mode) - with options
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"click": {
"selector": "button.load-more",
"timeout": 20000,
"delay": "500ms",
"scroll": true,
"visible": true,
"count": 2
}
}]
}'
# Click at an offset from an element
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"click": {
"selector": "#container",
"offset_x": 10,
"offset_y": 10
}
}]
}'
```
Press a keyboard key like Enter, Tab, Escape, or arrow keys.
**Parameters:**
**Direct form** - Pass key name as a string:
* `press` (required) - Key name to press (e.g., "Enter", "Tab", "Escape", "ArrowDown")
**Extended form** - Use object with options:
* `key` (required) - Key name to press (string)
* `delay` (optional) - Delay in milliseconds before pressing (number, default: 0)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - press Enter key
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"press": "Enter" # Press Enter key
}
]
)
# Extended form - press with delay
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"press": {
"key": "Tab",
"delay": 500
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - press Enter key
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
press: "Enter" // Press Enter key
}
]
});
// Extended form - press with delay
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
press: {
key: "Tab",
delay: 500
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"press": "Enter"
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"press": {
"key": "Tab",
"delay": 500
}
}]
}'
```
Fill an input field with text. Supports both typing (character-by-character) and pasting (instant) modes.
**Parameters:**
**Extended form only** - No direct form available:
* `selector` (required) - CSS selector of the input element (string)
* `value` (required) - Text value to fill (string)
* `mode` (optional) - Fill mode: `'type'` for character-by-character (default), `'paste'` for instant (string)
* `timeout` (optional) - Maximum time to wait for element in milliseconds (number, default: 30000)
* `typing_interval` (optional) - Interval between keystrokes in milliseconds when typing (number, default: 100, min: 10, max: 1000)
* `click_on_element` (optional) - Whether to click on element to focus it (boolean, default: `true`)
* `scroll` (optional) - Whether to scroll element into view (boolean, default: `true`)
* `visible` (optional) - Whether element must be visible (boolean, default: `true`)
* `delay` (optional) - Delay in milliseconds before filling (number, default: 0)
* `mouse_movement_strategy` (optional) - Mouse movement strategy for behavioral simulation (advanced)
* `typing_strategy` (optional) - Typing strategy for behavioral simulation (advanced)
* `required` (optional) - Set to `false` to make this step optional (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Type mode (default, character-by-character)
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fill": {
"selector": "input[name='search']",
"value": "eggplant",
"mode": "type", # Default mode
"typing_interval": 100, # 100ms between keystrokes
"click_on_element": True,
"scroll": True
}
}
]
)
# Paste mode (instant, faster)
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fill": {
"selector": "textarea",
"value": "Long text content...",
"mode": "paste", # Instant paste
"timeout": 30000
}
}
]
)
# Fill with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fill": {
"selector": "input",
"value": "text",
"scroll": True,
"delay": 500,
"typing_interval": 150,
"visible": True
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Type mode (default, character-by-character)
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fill: {
selector: "input[name='search']",
value: "eggplant",
mode: "type", // Default mode
typing_interval: 100, // 100ms between keystrokes
click_on_element: true,
scroll: true
}
}
]
});
// Paste mode (instant, faster)
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fill: {
selector: "textarea",
value: "Long text content...",
mode: "paste", // Instant paste
timeout: 30000
}
}
]
});
// Fill with options
const result3 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fill: {
selector: "input",
value: "text",
scroll: true,
delay: 500,
typing_interval: 150,
visible: true
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Type mode (default)
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"fill": {
"selector": "input[name='\''search'\'']",
"value": "eggplant",
"mode": "type",
"typing_interval": 100,
"click_on_element": true,
"scroll": true
}
}]
}'
# Paste mode
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"fill": {
"selector": "textarea",
"value": "Long text content...",
"mode": "paste",
"timeout": 30000
}
}]
}'
# Fill with options
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"fill": {
"selector": "input",
"value": "text",
"scroll": true,
"delay": 500,
"typing_interval": 150,
"visible": true
}
}]
}'
```
Scroll the page by pixels, to a position, or to an element.
**Parameters:**
**Direct form** - Pass number or string:
* `scroll: 500` - Scroll Y pixels (number, positive = down, negative = up)
* `scroll: "bottom"` - Scroll to bottom of page (string)
* `scroll: "#comments"` - Scroll to element matching selector (string)
**Extended form** - Use object with options:
* `y` (optional) - Vertical scroll distance in pixels (number, default: 0)
* `x` (optional) - Horizontal scroll distance in pixels (number, default: 0)
* `to` (optional) - CSS selector to scroll to (string, alternative to x/y)
* `container` (optional) - CSS selector of container element to scroll within (string)
* `visible` (optional) - Whether element must be visible when scrolling to it (boolean, default: `true`)
* `required` (optional) - Set to `false` to make this step optional (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - scroll down 500px
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": 500 # Scroll down 500 pixels
}
]
)
# Direct form - scroll up 200px
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": -200 # Scroll up 200 pixels
}
]
)
# Direct form - scroll to bottom
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": "bottom" # Scroll to page bottom
}
]
)
# Direct form - scroll to element
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": "#comments" # Scroll to comments section
}
]
)
# Extended form - scroll by pixels
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": {
"x": 100,
"y": 500
}
}
]
)
# Extended form - scroll to element in container
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"scroll": {
"to": "#target",
"container": "#modal",
"visible": True
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - scroll down 500px
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: 500 // Scroll down 500 pixels
}
]
});
// Direct form - scroll up 200px
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: -200 // Scroll up 200 pixels
}
]
});
// Direct form - scroll to bottom
const result3 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: "bottom" // Scroll to page bottom
}
]
});
// Direct form - scroll to element
const result4 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: "#comments" // Scroll to comments section
}
]
});
// Extended form - scroll by pixels
const result5 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: {
x: 100,
y: 500
}
}
]
});
// Extended form - scroll to element in container
const result6 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
scroll: {
to: "#target",
container: "#modal",
visible: true
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form - scroll down 500px
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": 500
}]
}'
# Direct form - scroll up 200px
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": -200
}]
}'
# Direct form - scroll to bottom
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": "bottom"
}]
}'
# Direct form - scroll to element
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": "#comments"
}]
}'
# Extended form - scroll by pixels
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": {
"x": 100,
"y": 500
}
}]
}'
# Extended form - scroll to element in container
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"scroll": {
"to": "#target",
"container": "#modal",
"visible": true
}
}]
}'
```
Automatically scroll the page to load dynamic content (infinite scroll). Continuously scrolls until content stops loading or timeout is reached.
**Parameters:**
**Direct form** - Pass boolean or number:
* `auto_scroll: true` - Use default settings
* `auto_scroll: 30000` - Max duration in milliseconds
**Extended form** - Use object with options:
* `max_duration` (optional) - Maximum duration in milliseconds to scroll (number, default: 25000)
* `delay_after_scroll` (optional) - Delay in milliseconds between scroll steps (number, default: 200)
* `step_size` (optional) - Pixels to scroll per step (number, defaults to viewport height)
* `click_selector` (optional) - CSS selector to click before scrolling (string, e.g., "Load More" button)
* `container` (optional) - CSS selector of container element to scroll within (string)
* `idle_timeout` (optional) - Stop scrolling after this many milliseconds of no new content (number, default: 7000)
* `pause_on_selector` (optional) - Pause scrolling if this selector appears above the cursor position (string)
* `required` (optional) - Set to `false` to make this step optional (boolean, default: `true`)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - boolean (use defaults)
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"auto_scroll": True # Auto scroll with default settings
}
]
)
# Direct form - number (max duration)
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"auto_scroll": 30000 # Auto scroll for max 30 seconds
}
]
)
# Extended form - with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"auto_scroll": {
"max_duration": 30000,
"container": "#feed",
"click_selector": ".load-more",
"delay_after_scroll": 500,
"idle_timeout": 7000
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - boolean (use defaults)
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
auto_scroll: true // Auto scroll with default settings
}
]
});
// Direct form - number (max duration)
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
auto_scroll: 30000 // Auto scroll for max 30 seconds
}
]
});
// Extended form - with options
const result3 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
auto_scroll: {
max_duration: 30000,
container: "#feed",
click_selector: ".load-more",
delay_after_scroll: 500,
idle_timeout: 7000
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form - boolean
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"auto_scroll": true
}]
}'
# Direct form - number
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"auto_scroll": 30000
}]
}'
# Extended form - with options
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"auto_scroll": {
"max_duration": 30000,
"container": "#feed",
"click_selector": ".load-more",
"delay_after_scroll": 500,
"idle_timeout": 7000
}
}]
}'
```
Auto scroll continuously scrolls the page until `idle_timeout` milliseconds pass with no new content appearing, or until `max_duration` is reached. Use `click_selector` to handle "Load More" buttons.
Capture a screenshot of the page and return as base64-encoded image.
**Parameters:**
**Direct form** - Pass boolean to capture full page screenshot:
* `screenshot` (required) - Set to `true` to capture full page PNG screenshot
**Extended form** - Use object with options:
* `full_page` (optional) - Capture full scrollable page (boolean, default: `true`)
* `format` (optional) - Image format: `'png'` (default), `'jpeg'`, or `'webp'`
* `quality` (optional) - Image quality for jpeg/webp (number, 0-100, default: 90)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - full page PNG screenshot
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"screenshot": True
}
]
)
# Extended form - with options
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"screenshot": {
"full_page": False,
"format": "jpeg",
"quality": 90
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - full page PNG screenshot
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
screenshot: true
}
]
});
// Extended form - with options
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
screenshot: {
full_page: false,
format: "jpeg",
quality: 90
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"screenshot": true
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"screenshot": {
"full_page": false,
"format": "jpeg",
"quality": 90
}
}]
}'
```
Retrieve all cookies set in the browser for the current page.
**Parameters:**
**Direct form** - Pass boolean to get all cookies:
* `get_cookies` (required) - Set to `true` to retrieve all cookies
**Extended form** - Use object with options:
* `domain` (optional) - Domain filter for cookies (string, e.g., ".example.com")
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - get all cookies
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"get_cookies": True
}
]
)
# Extended form - with domain filter
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"get_cookies": {
"domain": ".example.com"
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - get all cookies
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
get_cookies: true
}
]
});
// Extended form - with domain filter
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
get_cookies: {
domain: ".example.com"
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"get_cookies": true
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"get_cookies": {
"domain": ".example.com"
}
}]
}'
```
Make HTTP requests from the browser context and return the response.
**Parameters:**
**Direct form** - Pass URL as a string:
* `fetch` (required) - URL for the HTTP request (GET method)
**Extended form** - Use object with options:
* `url` (required) - URL to request (string)
* `method` (optional) - HTTP method: `'GET'` (default), `'POST'`, `'PUT'`, `'DELETE'`, `'PATCH'`, etc.
* `headers` (optional) - HTTP headers to send as key-value object
* `body` (optional) - Request body as string (for POST, PUT, PATCH)
* `timeout` (optional) - Maximum time in milliseconds to wait for response (number, default: 15000)
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Direct form - simple GET request
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fetch": "https://api.example.com/data" # Simple GET request
}
]
)
# Extended form - POST with headers and body
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fetch": {
"url": "https://api.example.com/submit",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": '{"key": "value"}',
"timeout": 15000
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Direct form - simple GET request
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fetch: "https://api.example.com/data" // Simple GET request
}
]
});
// Extended form - POST with headers and body
const result2 = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fetch: {
url: "https://api.example.com/submit",
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: '{"key": "value"}',
timeout: 15000
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
# Direct form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"fetch": "https://api.example.com/data"
}]
}'
# Extended form
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [{
"fetch": {
"url": "https://api.example.com/submit",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": "{\"key\": \"value\"}",
"timeout": 15000
}
}]
}'
```
The first `fetch` in a `browser_actions` is free. Starting from the second request, each additional `fetch` is billed as a VX6 request.
You can chain multiple actions together in the `browser_actions` array. They execute sequentially.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"fill": {
"selector": ["input[type='search']"],
"value": "laptop"
}
},
{
"wait": "1s"
},
{
"click": {
"selector": ["button[type='submit']"]
}
},
{
"wait_for_element": {
"selector": ".results",
"timeout": 5000
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from '@nimble-way/nimble-js';
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
fill: {
selector: ["input[type='search']"],
value: "laptop"
}
},
{
wait: "1s"
},
{
click: {
selector: ["button[type='submit']"]
}
},
{
wait_for_element: {
selector: [".results"],
timeout: 5000
}
}
]
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [
{
"fill": {
"selector": ["input[type='\''search'\'']"],
"value": "laptop"
}
},
{
"wait": "1s"
},
{
"click": {
"selector": ["button[type='\''submit'\'']"]
}
},
{
"wait_for_element": {
"selector": ".results",
"timeout": 5000
}
}
]
}'
```
## Example response
When browser actions complete successfully, you'll receive the final page state along with any data captured.
The response includes:
* **data**: All related extacted data
* **data.html**: Final DOM state after all actions
* **data.screenshot**: Page screenshot if used
* **data.cookies**: Collected cookies if `get_cookies` was used by order
* **data.fetch**: HTTP response if `fetch` was used by order
* **metadata**: Execution details including task id, driver used, execution time and more
* **metadata.browser\_actions**: The browser actions results per step
```json theme={"system"}
{
"url": "https://www.example.com/",
"task_id": "b1fa7943-cba5-4ec2-a88c-4d2d6799c794",
"status": "success",
"data": {
"html": "...",
"screenshot": "base64_encoded_image_data",
"cookies": [
{
"name": "session_id",
"value": "abc123xyz",
"domain": ".example.com",
"path": "/",
"expires": 1735689600,
"httpOnly": true,
"secure": true
}
],
"fetch": [
{
"status": 200,
"duration": 235,
"method": "POST",
"url": "https://api.example.com/submit",
"headers": {},
"body": "..."
}
]
},
"metadata": {
"query_time": "2026-02-08T22:00:36.132Z",
"query_duration": 1877,
"response_parameters": {
"input_url": "https://www.example.com/"
},
"driver": "vx6"
},
"status_code": 200
}
```
## Best practices
### Use required parameter for optional steps
**Make non-critical steps optional:**
```python theme={"system"}
# ✅ Continue even if optional element isn't found
browser_actions = [
{
"click": {
"selector": [".cookie-banner button"],
"required": False # Don't fail if banner doesn't exist
}
},
{
"click": {
"selector": [".submit-button"],
"required": True # This is critical
}
}
]
# ❌ Fail entire request if cookie banner isn't found
browser_actions = [
{
"click": {
"selector": [".cookie-banner button"]
# required defaults to True
}
}
]
```
### Chain actions with appropriate waits
**Add delays between actions:**
```python theme={"system"}
# ✅ Wait for content to load between actions
browser_actions = [
{
"click": {
"selector": ["button.load-more"]
}
},
{
"wait": "2s" # Wait for new content to load
},
{
"wait_for_element": {
"selector": ".new-content"
}
}
]
# ❌ No wait - may act on stale content
browser_actions = [
{
"click": {
"selector": ["button.load-more"]
}
},
{
"click": {
"selector": [".new-content button"] # May not exist yet
}
}
]
```
### Use specific selectors
**Be precise with CSS selectors:**
```python theme={"system"}
# ✅ Specific selector
selector = "button[data-testid='submit-form']"
# ✅ Unique class or ID
selector = "#unique-submit-button"
# ❌ Too generic - may match wrong element
selector = "button"
# ❌ Fragile - breaks with style changes
selector = ".btn.btn-primary.btn-lg.mt-4"
```
### Optimize auto scroll parameters
**Set appropriate scroll limits:**
```python theme={"system"}
# ✅ Controlled scrolling with limits
auto_scroll = {
"max_duration": 10000, # 10 seconds max
"idle_timeout": 3000, # Stop if no change for 3s
"delay_after_scroll": 500, # Wait between scrolls
"click_selector": ".load-more" # Handle load more buttons
}
# ❌ Excessive scrolling - slow and costly
auto_scroll = {
"max_duration": 60000, # 60 seconds - too long
"delay_after_scroll": 100 # Too fast - may miss content
}
```
# File Parsing
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/file-parsing
Convert PDFs, Word documents, spreadsheets, and presentations into markdown during extraction
File parsing converts documents into clean markdown. Add `file_parsing_options` to an Extract request that points at a document URL.
Enabling file parsing adds a per-page-parsed charge on top of the base Extract price. Billing is per page parsed, not per document, and depends on the mode. See [Pricing](/nimble-sdk/admin/pricing).
## Quick Start
### Example Request
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://example.com/report.pdf",
"formats": ["markdown"],
"file_parsing_options": {
"mode": "pro",
"max_pages": 50
}
}'
```
### Example Response
```json theme={"system"}
{
"url": "https://example.com/report.pdf",
"task_id": "8e8cfde7-b311-42f1-8a1c-8f5b6a1c1d11",
"status": "success",
"data": {
"markdown": "# Annual Report\n\n## Revenue\n\n| Quarter | Revenue |\n| --- | --- |\n| Q1 | $4.2M |\n..."
}
}
```
## Supported file types
| Type | Extensions |
| - | - |
| PDF | `.pdf` |
| Word documents | `.docx` |
| Spreadsheets | `.xlsx`, `.xls`, `.ods`, `.csv` |
| Presentations | `.pptx`, `.ppt`, `.odp` |
Nimble detects the file type from the response `Content-Type` header, the URL file extension, or the file contents. Word documents are detected from their contents. A URL does not need to end in an extension, except for CSV files, which need a `.csv` extension or a `text/csv` content type.
## How it works
Nimble fetches the document from the URL like any other Extract request.
If `max_pages` is set and a PDF has more pages, the request fails before parsing.
The document is converted to markdown using the selected `mode`.
The markdown is returned in `data.markdown`.
## Parameters
Enables file parsing. Omit the object to skip parsing and pay only the base Extract price.
```json theme={"system"}
"file_parsing_options": {
"mode": "pro",
"max_pages": 50
}
```
Parsing mode.
* `light` - Fast, text-native extraction. No OCR.
* `pro` - Adds LLM-based parsing with OCR fallback for scanned or table-heavy PDFs. Billed at a higher per-page rate than `light`. See [Pricing](/nimble-sdk/admin/pricing).
Maximum number of pages to parse. Minimum `1`. No limit by default.
Rejects PDFs with more pages than this limit with a `file exceeds limit` error. The PDF is not parsed and no parsing charge applies. It does not truncate. Other file types are not page-checked.
## Choosing a mode
| | `light` | `pro` |
| - | - | - |
| Best for | Plain-text documents with a simple, single-column layout | Text-heavy documents with simple tables, and scanned PDFs |
| Tables | Not a focus. Best with plain text. | Simple tables preserved in markdown |
| OCR | No | Yes, as a fallback |
| Cost | Lowest per page | Higher per page |
For example, a 100-page PDF in `pro` costs \$0.75 to parse (100 pages at \$7.50 per 1,000), plus the base Extract price.
## Best practices
* **Start with `light` for digital text.** Use it for text-based documents at high volume. Switch to `pro` if tables come back flat or scanned pages come back empty.
* **Try `pro` for scans.** `light` does not run OCR, so it cannot read pages that are images of text. Test a sample before a large run.
* **Set `max_pages` when PDF sizes vary.** An unexpectedly large PDF is rejected before it is parsed. Other file types are not page-checked.
* **Request only markdown.** Set `formats` to `["markdown"]` to leave the original file out of the response.
* **Expect limited results from charts and dense visual layouts.** Neither mode is built to interpret charts, diagrams, or heavily designed pages.
## Response behavior
* When parsing succeeds, the parsed document is returned in `data.markdown`, whatever `formats` is set to.
* The original file is returned base64-encoded in `data.html` when `formats` includes `html` or when `formats` is not set. Set `formats` to `["markdown"]` to leave it out.
* If parsing fails, the request still succeeds with a warning in the response, and the failed parse is not billed. If `html` was not requested, the original file is returned in `data.html` instead of an empty response.
## Errors
| Condition | Result |
| - | - |
| PDF has more pages than `max_pages` | `400` with `file exceeds limit: pages, max_pages is `. No parsing charge applies. Raise `max_pages` or omit it. |
## Next steps
Control which data types are returned
Per-page rates for each mode
# Formats
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/formats
Control response format and included data types
Format options control what data types are included in your response. Specify one or more formats to receive HTML, markdown, screenshots, response headers, or extracted links alongside your extracted data.
Common uses:
* **Full page content**: Get raw HTML for custom processing
* **Readable text**: Convert pages to clean markdown format
* **Visual records**: Capture screenshots for monitoring or archival
* **Link extraction**: Get all URLs from the page for further crawling
* **Response headers**: Inspect HTTP headers returned by the server
You can combine multiple formats in a single request. All specified formats
will be included in the response.
## Parameters
Choose which types of content you want in your response. You can request multiple formats at once, and each will be included in the result.
**Available formats:**
* `html` - The full HTML source code of the page
* *Best for:* Custom parsing, preserving exact page structure, accessing all DOM elements
* `markdown` - Clean, readable markdown version of the page
* *Best for:* Content analysis, LLM processing, human-readable output
* `screenshot` - Full-page screenshot as base64-encoded PNG
* *Best for:* Visual verification, monitoring, archival, debugging
* *Note:* Automatically enables rendering (VX8/VX10 driver)
* `headers` - HTTP response headers returned by the server
* *Best for:* Inspecting content type, caching policies, redirects, server metadata
* *Returns:* Key-value object under `data.headers`
* `links` - All URLs found on the page
* *Best for:* Discovering pages to crawl, mapping site structure, finding external references
* *Returns:* Array of URL strings under `data.links`
**Example:**
```json theme={"system"}
"formats": ["html", "markdown", "screenshot", "headers", "links"]
```
## Usage
### HTML format
Request one format type - html (default). **Best for:**
* Custom HTML parsing
* Preserving exact page structure
* Accessing all DOM elements and attributes
* Archival purposes
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://www.google.com/search?q=nimble",
formats= ["html"] # default
)
# Access HTML content
html_content = result.data.html
print(html_content)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.google.com/search?q=nimble",
formats: ["html"], // default
});
// Access HTML content
const htmlContent = result.data.html;
console.log(htmlContent);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.google.com/search?q=nimble",
"formats": ["html"]
}'
```
### Markdown format
Convert the page to clean, readable markdown. **Best for:**
* Clean text extraction
* Content analysis
* LLM processing
* Human-readable output:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://www.amazon.com/s?k=ironflask",
formats= ["markdown"]
)
# Access markdown content
markdown_content = result.data.markdown
print(markdown_content)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.amazon.com/s?k=ironflask",
formats: ["markdown"],
});
// Access markdown content
const markdownContent = result.data.markdown;
console.log(markdownContent);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.amazon.com/s?k=ironflask",
"formats": ["markdown"]
}'
```
### Screenshot format
Capture a full-page screenshot as a base64-encoded PNG image. **Best for:**
* Visual verification and monitoring
* Page archival and documentation
* Comparing page changes over time
Screenshot format automatically enables JavaScript rendering (VX8 or VX10
driver), which may affect pricing. See [Pricing](/nimble-sdk/admin/pricing)
for driver costs.
```python Python theme={"system"}
from nimble_python import Nimble
import base64
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.nimbleway.com",
formats=["screenshot"]
)
# Access screenshot (base64-encoded PNG)
screenshot_base64 = result.data.screenshots[0]
# Save to file
with open("screenshot.png", "wb") as f:
f.write(base64.b64decode(screenshot_base64))
print("Screenshot saved to screenshot.png")
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
import fs from "fs";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.nimbleway.com",
formats: ["screenshot"],
});
// Access screenshot (base64-encoded PNG)
const screenshotBase64 = result.data?.screenshots?.[0] as string;
// Save to file
fs.writeFileSync("screenshot.png", Buffer.from(screenshotBase64, "base64"));
console.log("Screenshot saved to screenshot.png");
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"formats": ["screenshot"]
}'
```
### Headers format
Return the HTTP response headers from the server. **Best for:**
* Inspecting `Content-Type`, `Cache-Control`, and redirect behavior
* Debugging server responses and middleware
* Verifying geo-targeted responses return the expected locale or region
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
formats=["headers"]
)
# Access response headers
headers = result.data.headers
print(headers)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
formats: ["headers"],
});
// Access response headers
const headers = result.data.headers;
console.log(headers);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"formats": ["headers"]
}'
```
### Links format
Extract all URLs found in the page HTML. **Best for:**
* Discovering new pages to crawl or extract
* Mapping internal site structure
* Finding outbound links and external references
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
formats=["links"]
)
# Access extracted links
links = result.data.links
for link in links:
print(link)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
formats: ["links"],
});
// Access extracted links
const links = result.data?.links ?? [];
links.forEach((link) => console.log(link));
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"formats": ["links"]
}'
```
### Multiple formats
Combine multiple formats to get different data representations:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.nimbleway.com",
formats=["html", "markdown", "screenshot"]
)
print(result.data.html)
print(result.data.markdown)
print(result.data.screenshots[0]) # base64-encoded PNG
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.nimbleway.com",
formats: ["html", "markdown", "screenshot"],
});
console.log(result.data.html);
console.log(result.data.markdown);
console.log(result.data?.screenshots?.[0]); // base64-encoded PNG
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.nimbleway.com",
"formats": ["html", "markdown", "screenshot"]
}'
```
When combining screenshot with other formats, rendering is automatically
enabled for all formats in the request.
## Example response
When formats are specified, all requested data is included in the response. The response includes:
* **data.html**: Raw HTML if requested
* **data.markdown**: Converted markdown if requested
* **data.screenshots**: Base64-encoded PNG images if requested
* **data.links**: Array of extracted URLs if requested
* **data.headers**: HTTP response headers key-value object if requested
* **data.parsing**: Structured data if parsing was used
* **metadata**: Execution details and formats included:
```json theme={"system"}
{
"url": "https://www.example.com/",
"task_id": "b1fa7943-cba5-4ec2-a88c-4d2d6799c794",
"status": "success",
"data": {
"html": "......",
"markdown": "# Article Title\n\nThis is the article content...",
"screenshot": "iVBORw0KGgoAAAANSUhEUgAAA...",
"headers": {
"content-type": "text/html; charset=utf-8",
"cache-control": "max-age=3600",
"content-language": "en-US",
"server": "nginx"
},
"links": [
"https://www.example.com/about",
"https://www.example.com/contact",
"https://www.example.com/products",
"https://external-site.com"
],
"parsing": {
"title": "Example Article",
"author": "John Doe"
}
},
"metadata": {
"query_time": "2026-02-08T22:00:36.132Z",
"query_duration": 1877,
"response_parameters": {
"input_url": "https://www.example.com/"
},
"driver": "vx6"
},
"status_code": 200
}
```
## Best practices
### Format selection
**Choose formats based on your needs:**
* Use `html` when you need full DOM access
* Use `markdown` for clean text and content analysis
* Use `screenshot` for visual verification
* Use `links` for discovering URLs to crawl
* Use `headers` for inspecting server metadata, content type, or caching behavior
**Avoid unnecessary formats:**
```python theme={"system"}
# ❌ Don't request all formats if you only need one
formats=["html", "markdown"]
# ✅ Request only what you need
formats=["markdown"]
```
### Performance considerations
* Each format adds processing time
* Screenshots require rendering and are slower
* HTML and markdown are faster to generate
* Request only needed formats for optimal performance
# Geo Targeting
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/geo-targeting
Built-in residential proxy allowing Geo-targeting options
Geo Targeting routes each request through a proxy exit in a specific region. Use it when content changes by location or language.
Common uses:
* **Localized content**: pricing, availability, shipping, store pages.
* **SEO & market research**: region-specific SERPs and ads.
* **Fewer blocks**: look like a real local user with residential exits.
## Parameters
Make your request appear as if it's coming from a specific country. Perfect for accessing region-specific content like localized prices or availability.
**When to use:**
* E-commerce sites with different prices by country
* Content that varies by region
* Accessing geo-restricted pages
* SEO analysis for different markets
Use ISO Alpha-2 Country Codes i.e. `US`, `DE`, `GB`.\
Use `ALL` for random country selection.
**Example:**
```json theme={"system"}
"country": "GB"
```
For US or CA locations only - specify which state your request should come from.
Use ISO Alpha-2 Country Codes i.e. `NY`, `AZ`, `CA`
**Note:** Only works when `country` is set to `US` or `CA`. Useful for state-specific content like local store inventory or regional pricing.
**Example:**
```json theme={"system"}
"country": "US",
"state": "CA"
```
Target a specific city for hyper-local content. Works best when combined with country (and state for US cities).
**When to use:**
* Local business listings
* City-specific promotions
* Store availability by location
* Hyper-local content
**Examples:** `"new_york"`, `"london"`, `"paris"`
**Example:**
```json theme={"system"}
"country": "US",
"state": "NY",
"city": "new_york"
```
Replace spaces from city names with underscore (e.g,`New York` becomes `new_york`).
Set the browser's preferred language. This changes the `Accept-Language` header and tells websites which language you prefer.
**Most common locales:**
* `en-US` - English (United States)
* `en-GB` - English (United Kingdom)
* `fr-FR` - French (France)
* `de-DE` - German (Germany)
* `es-ES` - Spanish (Spain)
* `ja-JP` - Japanese (Japan)
* `zh-CN` - Chinese (China)
* `pt-BR` - Portuguese (Brazil)
* `auto` - Automatically match the country
**Best practice:** Match your locale to your country (e.g., `fr-FR` with `country: "FR"`) for the most authentic experience.
**Example:**
```json theme={"system"}
"country": "FR",
"locale": "fr-FR"
```
`country`/`state`/`city` geo targeting changes **where** the request comes
from. `locale` changes **which language** the browser prefers.
## Usage
This request exits from **New York** and sets the browser language to **English**.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://ipinfo.io/json",
country="US",
state="NY",
locale="en-US",
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://ipinfo.io/json",
country: "US",
state: "NY",
locale: "en-US",
});
console.log(result.data.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://ipinfo.io/json",
"country": "US",
"state": "NY",
"locale": "en-US"
}'
```
## Best practices
### Use geo targeting when content varies by location
Only specify country/state/city when the content differs by location.
```python theme={"system"}
# ✅ Use for location-specific content
result = nimble.extract.run({
"url": "https://www.example.com/products",
"country": "GB" # Prices and availability vary by country
})
# ❌ Unnecessary for global content
result = nimble.extract.run({
"url": "https://www.example.com/about",
"country": "DE" # About page is the same everywhere
})
```
### Match locale to target country
Set the locale to match the country for accurate localized content.
```python theme={"system"}
# ✅ Locale matches country
result = nimble.extract.run({
"url": "https://www.example.com",
"country": "FR",
"locale": "fr-FR"
})
# ✅ Locale auto matching country
result = nimble.extract.run({
"url": "https://www.example.com",
"country": "FR",
"locale": "auto"
})
# ❌ Locale doesn't match country
result = nimble.extract.run({
"url": "https://www.example.com",
"country": "FR",
"locale": "en-US" # May not get French content
})
```
### Start with country-level targeting
Use country-level targeting first, then add state/city if needed.
```python theme={"system"}
# ✅ Start simple with country
result = nimble.extract.run({
"url": "https://www.example.com",
"country": "US"
})
# ✅ Add state if content differs
result = nimble.extract.run({
"url": "https://www.example.com",
"country": "US",
"state": "NY" # Only if NY shows different content
})
# ❌ Overly specific when not needed
result = nimble.extract.run({
"url": "https://www.example.com/global-page",
"country": "US",
"state": "NY", # Unnecessary granularity
"city": "new_york"
})
```
# Custom Headers & Cookies
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/headers-and-cookies
Send custom headers and cookies to access personalized or authenticated content
Custom headers and cookies enable you to access personalized data, maintain user sessions, and interact with authenticated endpoints. Set headers and cookies in string or object format to mimic real user behavior and retrieve content that requires specific request contexts.
## When to use
Use custom headers and cookies when you need to:
* **Access authenticated content**: Retrieve data behind login walls
* **Maintain sessions**: Preserve user state across requests
* **Mimic real users**: Include user-specific headers for personalized data
* **API authentication**: Send API keys or tokens in headers
* **Regional content**: Set language or region preferences
This is an advanced feature. Misconfigured headers or cookies may trigger
anti-bot detection or cause requests to fail.
## Parameters
Add custom HTTP headers to your request. Useful for authentication, setting custom user agents, or mimicking specific browsers.
**Common use cases:**
* API authentication (API keys, tokens)
* Custom User-Agent strings
* Accept-Language preferences
* Referer headers
* Custom authentication headers
**Format:** Provide headers as key-value pairs in an object.
**Example:**
```json theme={"system"}
"headers": {
"User-Agent": "MyApp/1.0",
"Accept-Language": "en-US,en;q=0.9",
"X-API-Key": "your-api-key"
}
```
The HTTP method to use for the request.
**Supported methods:** `GET`, `POST`, `PUT`, `DELETE`, `PATCH`
**When to use:**
* `GET` - Fetching data (default)
* `POST` - Submitting forms or data
* `PUT` - Updating resources
* `DELETE` - Removing resources
**Note:** When using `POST` or `PUT`, you'll typically also need to include a `body` parameter.
**Example:**
```json theme={"system"}
"method": "POST"
```
The data to send in the request body. Used with POST, PUT, or PATCH requests.
**Format:** Can be an object (auto-converted to JSON) or a string.
**Common uses:**
* Form submissions
* API payloads
* Search queries
* Data uploads
**Example:**
```json theme={"system"}
"method": "POST",
"body": {
"query": "product search",
"filters": ["category:electronics"]
}
```
Send cookies as a simple string, just like they appear in the browser. Separate multiple cookies with semicolons.
**When to use:**
* Quickest way to add cookies
* When you have a cookie string from browser dev tools
* Session IDs and authentication tokens
**Format:** `name1=value1;name2=value2;name3=value3`
**Example:**
```json theme={"system"}
"cookies": "session_id=abc123xyz;user_pref=en_US;logged_in=true"
```
Send cookies as an array of objects for more control. Each cookie can specify its domain.
**When to use:**
* Different cookies for different domains
* More precise cookie control
* Complex cookie setups
**Each cookie object needs:**
* `key` - Cookie name (required)
* `value` - Cookie value (required)
* `domain` - Cookie domain (optional, defaults to request URL domain)
**Example:**
```json theme={"system"}
"cookies": [
{
"key": "session_id",
"value": "abc123xyz",
"domain": "example.com"
},
{
"key": "user_token",
"value": "token456",
"domain": "api.example.com"
}
]
```
Headers and cookies **cannot** be used together. Please do not include
any cookies when sending custom headers.\ \ Just remember that cookies can be
provided in either string format OR array format, but not both at the same
time.
## Usage
### Send custom headers
Include headers as an object:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://api.example.com/data",
headers= {
"User-Agent": "MyApp/1.0",
"Accept-Language": "en-US,en;q=0.9",
"Some-Extra-Header": "some-extra-header"
}
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://api.example.com/data",
headers: {
"User-Agent": "MyApp/1.0",
"Accept-Language": "en-US,en;q=0.9",
"Some-Extra-Header": "some-extra-header",
},
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://api.example.com/data",
"headers": {
"User-Agent": "MyApp/1.0",
"Accept-Language": "en-US,en;q=0.9",
"Some-Extra-Header": "some-extra-header"
}
}'
```
### POST requests with headers
Send POST requests with custom headers and body:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://api.example.com/submit",
method= "POST",
headers= {
"Content-Type": "application/json",
"Some-Extra-Header": "some-extra-header"
},
body= {
"query": "product search",
"filters": ["category:electronics"]
}
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://api.example.com/submit",
method: "POST",
headers: {
"Content-Type": "application/json",
"Some-Extra-Header": "some-extra-header",
},
body: {
query: "product search",
filters: ["category:electronics"],
},
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://api.example.com/submit",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Some-Extra-Header": "some-extra-header"
},
"body": {
"query": "product search",
"filters": ["category:electronics"]
}
}'
```
### Send cookies (string format)
Use simple string format for multiple cookies:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://www.example.com/account",
cookies= "session_id=abc123xyz;user_pref=en_US;logged_in=true"
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com/account",
cookies: "session_id=abc123xyz;user_pref=en_US;logged_in=true",
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com/account",
"cookies": "session_id=abc123xyz;user_pref=en_US;logged_in=true"
}'
```
### Send cookies (object format)
Use object format for domain-specific cookies:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url= "https://www.example.com/dashboard",
cookies= [
{
"key": "session_id",
"value": "abc123xyz",
"domain": "example.com"
},
{
"key": "user_token",
"value": "token456",
"domain": "api.example.com"
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com/dashboard",
cookies: [
{
key: "session_id",
value: "abc123xyz",
domain: "example.com",
},
{
key: "user_token",
value: "token456",
domain: "api.example.com",
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com/dashboard",
"cookies": [
{
"key": "session_id",
"value": "abc123xyz",
"domain": "example.com"
},
{
"key": "user_token",
"value": "token456",
"domain": "api.example.com"
}
]
}'
```
### Capture cookies with browser actions and reuse
Use browser actions to interact with a page (like selecting a zip code), capture the resulting cookies, and pass them to subsequent requests:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Step 1: Perform browser actions and capture cookies
initial_result = nimble.extract.run(
url= "https://www.example.com",
render= True,
browser_actions= [
{
"click": {
"selector": "#location-selector"
}
},
{
"fill": {
"selector": "#zipcode-input",
"value": "90210"
}
},
{
"click": {
"selector": "#submit-location"
}
},
{
"wait": 2000
},
{
"get_cookies": True
}
]
)
# Extract cookies from the response
captured_cookies = initial_result.data.cookies
# Step 2: Use captured cookies in subsequent requests
# Convert cookies array to string format
cookie_string = ";".join([f"{cookie['name']}={cookie['value']}" for cookie in captured_cookies])
result = nimble.extract.run(
url= "https://www.example.com/products",
cookies= cookie_string
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Step 1: Perform browser actions and capture cookies
const initialResult = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
click: {
selector: "#location-selector",
},
},
{
fill: {
selector: "#zipcode-input",
value: "90210",
},
},
{
click: {
selector: "#submit-location",
},
},
{
wait: 2000,
},
{
get_cookies: true,
},
],
});
// Extract cookies from the response
const capturedCookies = (initialResult.data?.cookies ?? []) as any[];
// Step 2: Use captured cookies in subsequent requests
// Convert cookies array to string format
const cookieString = capturedCookies
.map((cookie) => `${cookie.name}=${cookie.value}`)
.join(";");
const result = await nimble.extract.run({
url: "https://www.example.com/products",
cookies: cookieString,
});
console.log(result);
```
```bash cURL theme={"system"}
# Step 1: Perform browser actions and capture cookies
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [
{
"click": {
"selector": "#location-selector"
}
},
{
"fill": {
"selector": "#zipcode-input",
"value": "90210"
}
},
{
"click": {
"selector": "#submit-location"
}
},
{
"wait": 2000
},
{
"get_cookies": true
}
]
}'
# Response will include cookies in data.cookies array:
# {
# "status": "success",
# "data": {
# "html": "...",
# "cookies": [
# {
# "name": "location_zipcode",
# "value": "90210",
# "domain": ".example.com"
# },
# {
# "name": "session_id",
# "value": "abc123xyz",
# "domain": ".example.com"
# }
# ]
# }
# }
# Step 2: Use captured cookies in subsequent requests
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com/products",
"cookies": "location_zipcode=90210;session_id=abc123xyz"
}'
```
This workflow is useful when you need to set user preferences (like location,
language, or filters) via browser interaction, then make multiple faster
requests with the same session state. See [Browser
Actions](/nimble-sdk/web-tools/extract/features/browser-actions) for more
interaction options.
## Important constraints
**Don't mix headers and cookies:**
```python theme={"system"}
# ❌ Don't send both in same request
result = nimble.extract.run({
"url": "https://www.example.com",
"headers": {"Some-Extra-Header": "some-extra-header"},
"cookies": "session_id=abc" # May cause conflicts
})
# ✅ Use one method
result = nimble.extract.run({
"url": "https://www.example.com",
"headers": {"Some-Extra-Header": "some-extra-header"}
})
```
**Detection risk:**
* Improper headers may trigger anti-bot detection
* Invalid cookies can cause request failures
* Test thoroughly before production use
* Monitor success rates when using custom headers/cookies
**Header size limits:**
* Total header size: 32 KB
* Individual header: 8 KB
* Cookie header: 4 KB
This is an advanced feature requiring careful configuration. Always test in a
development environment first.
# JavaScript Rendering
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/js-rendering
Control browser rendering for dynamic content and JavaScript execution
JavaScript rendering enables full browser execution to capture dynamically loaded content, handle user interactions, and process JavaScript-heavy websites. Combine it with [browser actions](/nimble-sdk/web-tools/extract/features/browser-actions) to control exactly when the page counts as "loaded".
## When to use
Use JavaScript rendering when you need to:
* **Load dynamic content**: Capture content loaded via AJAX or fetch requests
* **Execute JavaScript**: Process sites that require JS for content display
* **Handle SPAs**: Extract data from Single Page Applications (React, Vue, Angular)
* **Wait for interactions**: Capture DOM state after user actions
* **See final state**: Get the page as users see it, not raw HTML
## Parameters
Turn JavaScript rendering on or off. When enabled, the page loads in a real browser that executes JavaScript, just like when you visit it yourself.
**When to enable:**
* Single Page Applications (React, Vue, Angular)
* Content loaded via AJAX or fetch
* Sites that need JavaScript to display content
* When you need the page as users see it
**When to keep disabled (faster, cheaper):**
* Static HTML pages
* Simple websites without JavaScript
* When raw HTML is sufficient
**Auto mode:** Set `render: "auto"` to let Nimble decide per request. The optimization engine starts with the fastest, cheapest configuration and automatically escalates through more capable driver configurations (including full rendering and stealth) until the request succeeds for the target domain.
**Note:** Rendering requires vx8 or vx10. The vx6 driver doesn't support rendering.
**Example:**
```json theme={"system"}
"render": true
```
**Example (automatic driver selection):**
```json theme={"system"}
"render": "auto"
```
Customize how automatic driver selection escalates. Maps driver configuration names to the number of attempts to spend on each before advancing to the next one (0 skips it). The key order defines the escalation order.
Providing this parameter automatically opts the request into `render: "auto"` — you don't need to set `render` as well.
**Available configuration names:**
* `vx6-fast` - Plain HTTP request, no browser session (fastest)
* `vx6-stealth` - HTTP request with a persistent browser session
* `vx8` - Headless browser rendering
* `vx8-pro` - Headful browser rendering
* `vx10` - Stealth headless browser rendering
* `vx10-pro` - Stealth browser rendering with a persistent session (most effective)
**Rules:**
* Attempts per configuration: 0-10
* At least one configuration must have attempts > 0
* If a specific `driver` is also set, it wins and `auto_driver_configuration` is ignored
**Example:**
```json theme={"system"}
"auto_driver_configuration": {
"vx6-fast": 1,
"vx8": 3,
"vx10-pro": 2
}
```
This tries a plain HTTP request once, then headless rendering up to 3 times, then maximum stealth up to 2 times.
Fine-grained load control lives in [browser actions](/nimble-sdk/web-tools/extract/features/browser-actions), which run inside the rendering browser:
* `wait_for_navigation` - when to consider the page "loaded"
* `load` - the standard page load event, good for most pages
* `domcontentloaded` - as soon as HTML is ready, before images load (fastest)
* `networkidle2` - 2 or fewer network requests in the last 500ms, good for dynamic content
* `networkidle0` - zero network activity for 500ms (most thorough, slowest)
* `wait_for_element` - wait until a specific CSS selector appears
* `wait` - wait a fixed amount of time (e.g. `"2s"`)
**Example:**
```json theme={"system"}
"browser_actions": [
{ "wait_for_navigation": "networkidle0" },
{ "wait_for_element": ".product-grid" }
]
```
## Usage
### Enable basic rendering
Set `render: true` to enable JavaScript execution:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.google.com/search?q=nba+allstars+2026",
render=True
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.google.com/search?q=nba+allstars+2026",
render: true,
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.google.com/search?q=nba+allstars+2026",
"render": true
}'
```
When `render: false` (default), the API returns the raw HTML without
JavaScript execution, suitable for static pages.
### Automatic driver selection
Set `render: "auto"` to let Nimble pick the optimal configuration per domain. The engine starts cheap and fast, and escalates to rendering and stealth only when needed:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render="auto"
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: "auto",
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": "auto"
}'
```
To control how the engine escalates, provide `auto_driver_configuration` with the configurations to try and how many attempts to spend on each:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Try a plain HTTP request once, then headless rendering,
# then maximum stealth
result = nimble.extract.run(
url="https://www.example.com",
auto_driver_configuration={
"vx6-fast": 1,
"vx8": 3,
"vx10-pro": 2
}
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Try a plain HTTP request once, then headless rendering,
// then maximum stealth
const result = await nimble.extract.run({
url: "https://www.example.com",
auto_driver_configuration: {
"vx6-fast": 1,
vx8: 3,
"vx10-pro": 2,
},
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"auto_driver_configuration": {
"vx6-fast": 1,
"vx8": 3,
"vx10-pro": 2
}
}'
```
`auto_driver_configuration` implies `render: "auto"`, so you don't need to
set both. If you also set a specific `driver`, the driver wins and
`auto_driver_configuration` is ignored.
### Dynamic content loading
Wait for AJAX-loaded content with a `wait_for_navigation` browser action:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Wait for all network activity to stop
result = nimble.extract.run(
url="https://www.target.com/deals/all",
render=True,
browser_actions=[
{"wait_for_navigation": "networkidle0"}
]
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Wait for all network activity to stop
const result = await nimble.extract.run({
url: "https://www.target.com/deals/all",
render: true,
browser_actions: [{ wait_for_navigation: "networkidle0" }],
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.target.com/deals/all",
"render": true,
"browser_actions": [
{ "wait_for_navigation": "networkidle0" }
]
}'
```
To wait for a specific element instead — say a product grid rendered after an API call — use `wait_for_element`:
```json theme={"system"}
"browser_actions": [
{ "wait_for_element": ".product-grid" }
]
```
### Combining with browser actions
Rendering works seamlessly with the full [browser actions](/nimble-sdk/web-tools/extract/features/browser-actions) toolkit — click, scroll, fill, and wait in sequence:
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Render page, then perform actions
result = nimble.extract.run(
url="https://www.example.com",
render=True,
browser_actions=[
{
"click": {
"selector": "button.load-more",
"timeout": 5000
}
},
{
"wait": "2s"
},
{
"auto_scroll": 5000
}
]
)
print(result.data.html)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
// Render page, then perform actions
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
browser_actions: [
{
click: {
selector: "button.load-more",
timeout: 5000,
},
},
{
wait: "2s",
},
{
auto_scroll: 5000,
},
],
});
console.log(result.data?.html);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"browser_actions": [
{
"click": {
"selector": "button.load-more",
"timeout": 5000
}
},
{
"wait": "2s"
},
{
"auto_scroll": 5000
}
]
}'
```
## Best practices
### Choose the right load condition
* **`load`** — default for most websites; waits for the standard page load event.
* **`domcontentloaded`** — fastest; use when images and styles aren't needed.
* **`networkidle2`** — AJAX-heavy pages; waits until most requests finish.
* **`networkidle0`** — most thorough; use when you need everything loaded.
```json theme={"system"}
"browser_actions": [
{ "wait_for_navigation": "networkidle2" }
]
```
### Combine with network capture
Monitor and capture API calls during rendering:
```python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
# Render page and capture specific API calls
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"method": "GET",
"url": {
"type": "contains",
"value": "/api/products"
}
}
]
)
# Access captured network data
api_responses = result.data.network_capture
print(api_responses)
```
### Performance optimization
**Choose the minimal driver:**
```python theme={"system"}
# ❌ Don't use vx10 when vx8 works
result = nimble.extract.run(
url="https://simple-spa.com",
driver="vx10", # Unnecessary stealth features
render=True
)
# ✅ Use appropriate driver
result = nimble.extract.run(
url="https://simple-spa.com",
driver="vx8", # Sufficient for most SPAs
render=True
)
```
Or let `render: "auto"` pick for you — it escalates only as far as the target domain requires.
### When to skip rendering
Use non-rendering (vx6) when:
* **Static content**: HTML already contains all data
* **API endpoints**: Fetching JSON directly
* **High throughput needed**: Rendering is slower
* **Simple pages**: No JavaScript required
* **Cost optimization**: Non-rendering is cheaper
```python theme={"system"}
# ✅ Skip rendering for static pages
result = nimble.extract.run(
url="https://static-site.com/page.html",
render=False, # or omit (false is default)
)
```
# Network Capture
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/network-capture
Capture internal API calls and dynamic data without parsing HTML
Network Capture intercepts internal API calls made during webpage loading, giving you direct access to structured data in JSON format instead of parsing HTML.
Common uses:
* **Dynamic content**: Capture lazy-loaded data and real-time updates.
* **API access**: Interact directly with backend APIs bypassing UI rendering.
* **Performance**: Reduce overhead by accessing machine-readable responses.
* **Accuracy**: Get reliable data directly from API endpoints.
Network Capture requires page rendering to be enabled (`render: true`). For
XHR/AJAX calls that don't need rendering, use the `is_xhr` parameter instead.
## Parameters
Intercept and capture network requests made by the page. Perfect for accessing hidden APIs or getting data directly from backend calls instead of parsing HTML.
**Requirements:** Only works when `render: true`
**Each capture filter is an object with these options:**
* `method` - Filter by HTTP method (GET, POST, PUT, DELETE, etc.)
* Leave empty to capture any method
* `url.type` - How to match URLs
* `exact` - Match the complete URL exactly
* `contains` - Match URLs containing a specific string
* `url.value` - The URL or URL pattern to match
* `resource_type` - Filter by request type (array)
* Options: `xhr`, `fetch`, `stylesheet`, `script`, `document`, `image`
* Example: `["xhr", "fetch"]` to capture only AJAX/fetch requests
* `validation` - Validate response content (default: false)
* Ensures captured responses are valid
* `wait_for_requests_count` - Wait for this many matching requests (default: 0)
* Useful when you know how many API calls to expect
* `wait_for_requests_count_timeout` - How long to wait in seconds (default: 10)
* Timeout for waiting for the expected request count
**Pro tip:** Use `contains` with `/api/` to capture all API calls, or be specific with exact URLs.
**Example (capture specific API):**
```json theme={"system"}
"network_capture": [
{
"method": "GET",
"url": {
"type": "contains",
"value": "/api/products"
},
"resource_type": ["xhr", "fetch"]
}
]
```
**Example (multiple captures):**
```json theme={"system"}
"network_capture": [
{
"url": {
"type": "contains",
"value": "/graphql"
}
},
{
"url": {
"type": "exact",
"value": "https://api.example.com/data"
},
"wait_for_requests_count": 1
}
]
```
## Usage
### Filter by exact URL match
Capture a specific API endpoint by matching the complete URL.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"method": "GET",
"url": {
"type": "exact",
"value": "https://www.example.com/api/data"
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
network_capture: [
{
method: "GET",
url: {
type: "exact",
value: "https://www.example.com/api/data",
},
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"network_capture": [
{
"method": "GET",
"url": {
"type": "exact",
"value": "https://www.example.com/api/data"
}
}
]
}'
```
### Filter by URL pattern
Use `contains` to capture requests with URLs matching a pattern. This is useful for capturing file types (like `.css` or `.js`), requests with dynamic URL components, or when you don't know the exact URL.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"url": {
"type": "contains",
"value": "/graphql"
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
network_capture: [
{
url: {
type: "contains",
value: "/api/",
},
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"network_capture": [
{
"url": {
"type": "contains",
"value": "/api/"
}
}
]
}'
```
### Filter by resource type
Capture specific types of resources like XHR, Fetch, or Script requests.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"method": "GET",
"resource_type": ["xhr", "fetch"]
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
network_capture: [
{
method: "GET",
resource_type: ["xhr", "fetch"],
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"network_capture": [
{
"method": "GET",
"resource_type": ["xhr", "fetch"]
}
]
}'
```
### Multiple filters
Combine multiple filters to capture different request types in one call.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"method": "GET",
"url": {
"type": "exact",
"value": "https://www.example.com/api/resource"
}
},
{
"url": {
"type": "contains",
"value": ".css"
}
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
network_capture: [
{
method: "GET",
url: {
type: "exact",
value: "https://www.example.com/api/resource",
},
},
{
url: {
type: "contains",
value: ".css",
},
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"network_capture": [
{
"method": "GET",
"url": {
"type": "exact",
"value": "https://www.example.com/api/resource"
}
},
{
"url": {
"type": "contains",
"value": ".css"
}
}
]
}'
```
### Wait for requests
Use `wait_for_requests_count` to ensure you capture a minimum number of network requests. The request duration will be extended until the count is reached or the timeout expires.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com",
render=True,
network_capture=[
{
"method": "GET",
"resource_type": ["xhr", "script"],
"wait_for_requests_count": 3,
"wait_for_requests_count_timeout": 5
}
]
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com",
render: true,
network_capture: [
{
method: "GET",
resource_type: ["xhr", "script"],
wait_for_requests_count: 3,
wait_for_requests_count_timeout: 5,
},
],
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com",
"render": true,
"network_capture": [
{
"method": "GET",
"resource_type": ["xhr", "script"],
"wait_for_requests_count": 3,
"wait_for_requests_count_timeout": 5
}
]
}'
```
This configuration will wait up to 5 seconds to capture at least 3 network
requests matching the filter criteria.
## XHR without rendering
For direct API endpoints that don't require page rendering, use `is_xhr` for better performance.
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://api.example.com/endpoint",
is_xhr=True,
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://api.example.com/endpoint",
is_xhr: true,
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://api.example.com/endpoint",
"is_xhr": true
}'
```
`is_xhr` only works when `render` is `false`. It sends XHR-specific headers
and targets the API URL directly.
## Example response
When browser actions complete successfully, you'll receive the final page state along with any data captured.
The response includes:
* **data**: All related extacted data
* **data.html**: Final DOM state after all actions
* **data.network\_capture**: The network capture response by order
* **metadata**: Execution details including task id, driver used, execution time and more
```json theme={"system"}
{
"url": "https://www.example.com/",
"task_id": "b1fa7943-cba5-4ec2-a88c-4d2d6799c794",
"status": "success",
"data": {
"html": "...",
"network_capture": [
{
"filter": {
"method": "GET",
"resource_type": ["xhr", "script"]
},
"result": [
{
"request": {
"resource_type": "script",
"method": "GET",
"url": "https://www.example.com/script/0001.js",
"headers": {}
},
"response": {
"status": 200,
"headers": {},
"body": "..."
}
},
{
"request": {
"resource_type": "xhr",
"method": "GET",
"url": "https://www.example.com/script/0002.js",
"headers": {}
},
"response": {
"status": 200,
"headers": {},
"body": "..."
}
}
]
}
]
},
"metadata": {
"query_time": "2026-02-08T22:00:36.132Z",
"query_duration": 1877,
"response_parameters": {
"input_url": "https://www.example.com/"
},
"driver": "vx6"
},
"status_code": 200
}
```
## Best practices
### Use specific URL patterns
**Be specific with URL matching:**
```python theme={"system"}
# ✅ Specific pattern for API endpoints
network_capture = [
{
"url": {
"type": "contains",
"value": "/api/v1/products"
}
}
]
# ❌ Too broad - captures everything
network_capture = [
{
"url": {
"type": "contains",
"value": "/"
}
}
]
```
### Filter by resource type
**Narrow down to relevant resources:**
```python theme={"system"}
# ✅ Capture only XHR and Fetch requests
network_capture = [
{
"resource_type": ["xhr", "fetch"]
}
]
# ✅ Capture scripts and stylesheets
network_capture = [
{
"resource_type": ["script", "stylesheet"]
}
]
```
### Set appropriate wait counts
**Use wait\_for\_requests\_count for dynamic content:**
```python theme={"system"}
# ✅ Wait for specific number of requests
network_capture = [
{
"method": "GET",
"resource_type": ["xhr"],
"wait_for_requests_count": 3,
"wait_for_requests_count_timeout": 10
}
]
# ❌ No wait - may miss delayed requests
network_capture = [
{
"method": "GET",
"resource_type": ["xhr"]
}
]
```
### Use XHR mode for direct API calls
**Skip rendering when accessing APIs directly:**
```python theme={"system"}
# ✅ Direct API access without rendering
result = nimble.extract.run({
"url": "https://api.example.com/data",
"is_xhr": True
})
# ❌ Unnecessary rendering for API endpoints
result = nimble.extract.run({
"url": "https://api.example.com/data",
"render": True,
"network_capture": [...]
})
```
# Parsing Schema
Source: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/features/parsing-schema
Precise data extraction with powerful parser syntax
Parsing Schema gives you full control over data extraction using a comprehensive parser syntax. Define exact data structures for predictable, low-cost extraction from HTML, JSON, XML, and network captures.
Parsers are the complete recipe for processing web content into structured data. They combine:
* selectors - identify elements
* extractors - extract data from elements
* post-processors (optional) - transform the output
# **When to use**
Use parsing schema when you need:
* **Predictable extraction**: Same selectors extract same data every time
* **Full control**: Specify exact selectors paths and data types
* **High volume**: Process large datasets efficiently
Parsers may break when page structure or selectors change. Monitor source
pages and update parsers as needed.
# **Parameters**
**`Must be set to true`** to enable parsing. This tells Nimble you want to extract structured data using the parser you define.
When disabled, you'll just get raw HTML without structured extraction.
**Example:**
```json theme={"system"}
"parse": true
```
Your custom extraction recipe that defines exactly what data to pull from the page and how to structure it.
**Parser structure:**
Each field in your parser is a key-value pair where:
* **Key** - The name of the field in your output (like `"product_name"` or `"price"`)
* **Value** - An object that describes how to extract that field
**Every parser needs:**
1. `type` - What kind of parser to use
* `terminal` - Extract a single value (like one price)
* `terminal_list` - Extract multiple values (like a list of image URLs)
* `schema` - Extract a nested object (like product details)
* `schema_list` - Extract a list of objects (like multiple products)
* `or` - Try multiple strategies, use first one that works
* `and` - Combine multiple extraction strategies
* `const` - Return a fixed value
2. `selector` - How to find the element on the page
* Use CSS selectors (`.product-name`, `#price`, etc.)
* Or XPath, JSON paths for other data types
3. `extractor` - What data to grab from the element
* `text` - The text content
* `attr` - An attribute value (like `href` or `src`)
* `json` - Parse JSON data
* `raw` - The raw HTML
4. `post_processor` (optional) - Transform the data
* Convert to number, format dates, clean text, etc.
**Think of it as:** "Find THIS element, grab THIS data from it, and format it like THIS"
**Simple example:**
```json theme={"system"}
"parser": {
"title": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "h1.product-title"
},
"extractor": {
"type": "text"
}
}
}
```
**List example:**
```json theme={"system"}
"parser": {
"images": {
"type": "terminal_list",
"selector": {
"type": "css",
"css_selector": ".product-gallery img"
},
"extractor": {
"type": "attr",
"attr": "src"
}
}
}
```
### Usage
**Example parser structure:**
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.example.com/product",
parse=True,
parser={
"product_name": { # key - output field name
"type": "terminal", # parser type
"selector": { # how to find the element
"type": "css",
"css_selector": ".product-title"
},
"extractor": { # what to extract
"type": "text"
}
},
"price": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".price-value"
},
"extractor": {
"type": "text",
"post_processor": { # optional: transform the data
"type": "number"
}
}
}
}
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.example.com/product",
parse: true,
parser: {
product_name: {
// key - output field name
type: "terminal", // parser type
selector: {
// how to find the element
type: "css",
css_selector: ".product-title",
},
extractor: {
// what to extract
type: "text",
},
},
price: {
type: "terminal",
selector: {
type: "css",
css_selector: ".price-value",
},
extractor: {
type: "text",
post_processor: {
// optional: transform the data
type: "number",
},
},
},
},
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.example.com/product",
"parse": true,
"parser": {
"product_name": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".product-title"
},
"extractor": {
"type": "text"
}
},
"price": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".price-value"
},
"extractor": {
"type": "text",
"post_processor": {
"type": "number"
}
}
}
}
}'
```
### **Example Output**
```json theme={"system"}
{
"status": "success",
"data": {
"parsing": {
"product_name": "Wireless Headphones",
"price": 79.99
}
}
}
```
## Parser Types
Supported parsing types:
* `terminal` - Returns a single terminal/literal as output.
* `terminal_list` - Returns a list of literals instead of a single literal.
* `schema` - Returns a dictionary/JSON according to its field parsers.
* `schema_list` - Returns a list of dictionaries/JSONs instead of a single dictionary/JSON.
* `or` - Tries a sequence of parsers and returns the result of the first parser that returns a non-null value
* `and` - Runs a sequence of schema parsers and merges their results into a single output. All parsers execute on the same input, and results are combined (first non-null value wins for overlapping keys).
* `const` - Always returns its `value` regardless of the input. Useful for adding static data to your output.
### terminal
* The most basic parser.
```json theme={"system"}
{
"type": "terminal",
"selector": { ... },
"extractor": { ... } // Defaults to raw extractor if not specified
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".product-name"
},
"extractor": {
"type": "text"
}
}
```
### terminal\_list
Returns a list of literals instead of a single literal.
```json theme={"system"}
{
"type": "terminal_list",
"selector": { ... },
"extractor": { ... } // Defaults to raw extractor if not specified
}
```
```json theme={"system"}
{
"type": "terminal_list",
"selector": {
"type": "css",
"css_selector": ".product-gallery img"
},
"extractor": {
"type": "attr",
"attr": "src"
}
}
```
### schema
This is the most commonly used parser for structured data extraction.
```json theme={"system"}
{
"type": "schema",
"selector": { ... }, // Optional
"fields": {
"field_name": { /* Parser */ }
}
}
```
```json theme={"system"}
{
"type": "schema",
"selector": {
"type": "css",
"css_selector": ".product-card"
},
"fields": {
"name": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".product-name"
},
"extractor": {
"type": "text"
}
},
"price": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".price"
},
"extractor": {
"type": "text"
}
}
}
}
```
### schema\_list
Returns a list of dictionaries/JSONs instead of a single dictionary/JSON.
```json theme={"system"}
{
"type": "schema_list",
"selector": { ... }, // Optional
"fields": {
"field_name": { /* Parser */ }
}
}
```
The optional `position` attribute adds an index field to each item in the output list.
```json theme={"system"}
{
"type": "schema_list",
"selector": { ... },
"fields": {
"field_name": { /* Parser */ }
},
"position": {
"field_name": "index",
"start_from": 1 // Optional, defaults to 0
}
}
```
```json theme={"system"}
{
"type": "schema_list",
"selector": {
"type": "css",
"css_selector": ".product-item"
},
"fields": {
"name": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".product-name"
},
"extractor": {
"type": "text"
}
},
"price": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".price"
},
"extractor": {
"type": "text"
}
}
}
}
```
### or
Useful for handling variations in page structure.
```json theme={"system"}
{
"type": "or",
"parsers": [
{
/* Parser 1 */
},
{
/* Parser 2 */
},
{
/* Parser 3 */
}
]
}
```
```json theme={"system"}
{
"type": "or",
"parsers": [
{
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".sale-price"
},
"extractor": {
"type": "text"
}
},
{
"type": "terminal",
"selector": {
"type": "css",
"css_selector": ".regular-price"
},
"extractor": {
"type": "text"
}
}
]
}
```
### and
Runs a sequence of schema parsers and merges their results into a single output. All parsers execute on the same input, and results are combined (first non-null value wins for overlapping keys).
```json theme={"system"}
{
"type": "and",
"parsers": [
{
/* Schema Parser 1 */
},
{
/* Schema Parser 2 */
}
]
}
```
### const
Always returns its `value` regardless of the input. Useful for adding static data to your output.
```json theme={"system"}
{
"type": "const",
"value": "some_value"
}
```
## Parsing Selectors
Selectors identify elements (HTML, JSON, XML, Network) in the input web page. Supported selectors:
* `css` - Selects elements matching a [CSS selector](https://www.w3schools.com/css/css_selectors.asp).
* `xpath` - Enables powerful element selection using [XPath expressions](https://www.w3schools.com/xml/xpath_intro.asp). Particularly useful for XML documents like RSS feeds and sitemaps.
* `json` - Extracts JSON elements from the page. All subsequent selectors and extractors receive JSON instead of HTML.
* `sequence` - Combines multiple selectors in sequence. Useful for chaining different selector types.
* `parent` - Traverses up the DOM tree (for HTML) or context hierarchy (for JSON). Useful when you need to select a parent element after finding a specific child.
* `root` - Returns the original page (document). Often used with JSON selector to access fields like `network_capture`, `url`, or `html`.
### css
Selects elements matching a [CSS selector](https://www.w3schools.com/css/css_selectors.asp).
```json theme={"system"}
{
"type": "css",
"css_selector": "div.price"
}
```
```json theme={"system"}
// Select by class
{ "type": "css", "css_selector": ".product-name" }
// Select by ID
{ "type": "css", "css_selector": "#main-content" }
// Select by attribute
{ "type": "css", "css_selector": "a[data-product-id]" }
// Complex selector
{ "type": "css", "css_selector": "div.product > h2.title" }
```
### xpath
Enables powerful element selection using [XPath expressions](https://www.w3schools.com/xml/xpath_intro.asp). Particularly useful for XML documents like RSS feeds and sitemaps.
```json theme={"system"}
{
"type": "xpath",
"path": "//book[@category='fiction']"
}
```
* `//element` - Select all elements with the given name - `/root/child` -
Select child elements of root - `//element[@attr='value']` - Select by
attribute value - `//element[position()=1]` - Select first element -
`//*[local-name()='element']` - Select ignoring namespaces
```json theme={"system"}
{
"type": "terminal_list",
"selector": {
"type": "xpath",
"path": "//*[local-name()='loc']"
},
"extractor": {
"type": "text"
}
}
```
```json theme={"system"}
{
"type": "sequence",
"sequence": [
{
"type": "xpath",
"path": "//item"
},
{
"type": "xpath",
"path": ".//title"
}
]
}
```
### json
Extracts JSON elements from the page. All subsequent selectors and extractors receive JSON instead of HTML.
```json theme={"system"}
{
"type": "json",
"path": "nested.keys.in.the.json" // jsonpath
}
```
The `coercion_filter` field provides advanced control when dealing with multiple JSON objects. It uses JSONPath expressions to filter specific JSON objects.
```json theme={"system"}
{
"type": "json",
"coercion_filter": "$[1]", // Get the second JSON object
"path": "nested.keys"
}
```
```json theme={"system"}
{
"type": "json",
"coercion_filter": "$[?(@.type=='product')]",
"path": "name"
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "sequence",
"sequence": [
{
"type": "css",
"css_selector": "script[type='application/ld+json']"
},
{
"type": "json",
"path": "$.offers.price"
}
]
},
"extractor": {
"type": "raw"
}
}
```
### sequence
Combines multiple selectors in sequence. Useful for chaining different selector types.
```json theme={"system"}
{
"type": "sequence",
"sequence": [
{
/* Selector 1 */
},
{
/* Selector 2 */
},
{
/* Selector 3 */
}
]
}
```
```json theme={"system"}
{
"type": "sequence",
"sequence": [
{
"type": "css",
"css_selector": "script#product-data"
},
{
"type": "json",
"path": "$.product"
}
]
}
```
### parent
Traverses up the DOM tree (for HTML) or context hierarchy (for JSON). Useful when you need to select a parent element after finding a specific child.
```json theme={"system"}
{
"type": "parent",
"times": 1 // Number of levels to traverse (default: 1)
}
```
```json theme={"system"}
{
"type": "sequence",
"sequence": [
{
"type": "css",
"css_selector": "span.price"
},
{
"type": "parent",
"times": 2
}
]
}
```
### root
Returns the original page (document). Often used with JSON selector to access fields like `network_capture`, `url`, or `html`.
```json theme={"system"}
{
"type": "root"
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "sequence",
"sequence": [
{
"type": "root"
},
{
"type": "json",
"path": "$.url"
}
]
},
"extractor": {
"type": "raw"
}
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "sequence",
"sequence": [
{
"type": "root"
},
{
"type": "json",
"path": "$.network_capture"
}
]
},
"extractor": {
"type": "raw"
}
}
```
## Parsing Extractors
Extractors specify what data to extract from the selected element. Supported extractors:
* `text` - Extracts the text content of the element. Works with both HTML (CSS selectors) and XML (XPath selectors).
* `strip` (optional, boolean) - If it is set to `false`, leading and trailing whitespaces are preserved in the text. Default is `true`.
* `separator` (optional, string) - Specifies a separator string to use when joining text from different child elements. When extracting text from nested HTML elements, this separator will be inserted between text from different elements. If not specified, text from different elements is concatenated without a separator.
* `attr` - Extracts an attribute value from the element. Works with both HTML and XML elements. common attr:
* `href` - Links
* `src` - Images, scripts
* `data-*` - Custom data attributes
* `class` - CSS classes
* `id` - Element IDs
* `json` - Extracts JSON content using JSONPath.
* `raw` - Extracts an element as-is without coercion. JSON stays as JSON, strings stay as strings. Useful for advanced parsing with complex JSON selectors.
If no extractor is specified, the **raw** extractor is used by default.
### text
Extracts the text content of the element. This extractor works with both HTML elements (from CSS selectors) and XML elements (from XPath selectors).
You can use `strip=false` to keep leading and trailing whitespace characters. The default is to remove them.
The text extractor supports both HTML elements (BeautifulSoup Tag) from CSS
selectors and XML elements (lxml Element) from XPath selectors. This allows
you to use the same extractor regardless of whether you're parsing HTML or XML
documents.
**Basic usage:**
```json theme={"system"}
{
"type": "text",
"regex": "string" // Optional (deprecated), will return 1st match
}
```
```json theme={"system"}
{
"type": "text",
"regex": "string", // Optional (deprecated), will return 1st match
"strip": false
}
```
```json theme={"system"}
{
"type": "text",
"regex": "string", // Optional (deprecated), will return 1st match
"separator": " | "
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "xpath",
"path": "//book/title"
},
"extractor": {
"type": "text",
"strip": true
}
}
```
### attr
Extracts an attribute value from the element. Works with both HTML and XML elements. Common attributes: `href` ,`src` ,`data-*` ,`class` , `id`
```json theme={"system"}
{
"type": "attr",
"attr": "href"
}
```
```json theme={"system"}
{
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "img.product-image"
},
"extractor": {
"type": "attr",
"attr": "src"
}
}
```
### json
Extracts JSON content using JSONPath.
```json theme={"system"}
{
"type": "json",
"path": "nested.keys.in.the.json"
}
```
### raw
Extracts an element as-is without coercion. JSON stays as JSON, strings stay as strings. Useful for advanced parsing with complex JSON selectors.
```json theme={"system"}
{
"type": "raw"
}
```
## Parsing Post Processors
Post processors transform extractor output. Define them in the extractor's `post_processor` field. Supported post-procession options:
* `url` - Converts relative URLs to absolute URLs based on the page origin.
* `regex` - Transforms output using a [regular expression](https://regexr.com/). The optional `group` parameter extracts a specific capturing group (defaults to 0).
* `format` - Formats input into a string using Python's str.format, where the input is available as `{data}`.
* `date` - Formats dates to ISO format or custom format.
* `boolean` - Transforms output to boolean based on conditions: `contains`, `exists`, or `regex`. Use `not: true` to reverse the result.
* `number` - Coerces output to a number (int or float). Handles formatted numbers like `"1.5M"` → `1500000` or `"2,100"` → `2100`.
* `country` - Converts country names to country codes.
* `sequence` - Applies multiple post processors in sequence.
### url
Converts relative URLs to absolute URLs based on the page origin.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "url"
}
}
}
```
* Input: `"/news/article"` - Output: `"https://www.example.com/news/article"`
### regex
Transforms output using a [regular expression](https://regexr.com/). The optional `group` parameter extracts a specific capturing group (defaults to 0).
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "regex",
"regex": "\\d+",
"group": 0 // Optional
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "regex",
"regex": "\\d+\\.\\d+"
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "regex",
"regex": "Price: (\\d+)\\.(\\d+)",
"group": 1
}
}
}
```
### format
Formats input into a string using Python's str.format, where the input is available as `{data}`.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "format",
"format": "${data}"
}
}
}
```
- Input: 5.00 - Output: "\$5.00"
### date
Formats dates to ISO format or custom format.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "date",
"format": "%d/%m/%y" // Optional, defaults to ISO format
}
}
}
```
* Input: `"5 days ago"` - Output (no format): `"2024-07-29T00:00:00"` - Output
(with format `%d/%m/%y`): `"29/07/2024"`
### boolean
Transforms output to boolean based on conditions: `contains`, `exists`, or `regex`. Use `not: true` to reverse the result.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "boolean",
"condition": "contains",
"contains": "InStock"
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "boolean",
"condition": "exists"
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "boolean",
"condition": "regex",
"regex": "\\d+"
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "boolean",
"condition": "contains",
"contains": "OutOfStock",
"not": true
}
}
}
```
### number
Coerces output to a number (int or float). Handles formatted numbers like `"1.5M"` → `1500000` or `"2,100"` → `2100`.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "number",
"locale": "en", // Optional: locale for number parsing
"force_type": "float" // Optional: "int" or "float"
}
}
}
```
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "number",
"locale": "de"
}
}
}
```
Input: `"1.000,50"` → Output: `1000.50`
### country
Converts country names to country codes.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "country"
}
}
}
```
* Input: `"United States"` - Output: `"US"`
### sequence
Applies multiple post processors in sequence.
```json theme={"system"}
{
"extractor": {
"type": "text",
"post_processor": {
"type": "sequence",
"sequence": [
{
"type": "regex",
"regex": "\\d+\\.\\d+"
},
{
"type": "number"
}
]
}
}
}
```
* Input: `"The price is $50.25!"` - After regex: `"50.25"` - After number:
`50.25`
## Complete Examples
### Parsing a BBC News Article
This example demonstrates parsing a complete BBC news article about a three-legged cat, showing how to extract structured data from HTML using various parser types, selectors, and extractors.
**Target URL:** `https://www.bbc.com/news/articles/cervlxymly2o`
**Target Schema:**
```json theme={"system"}
{
"url": "string",
"title": "string",
"date": "string",
"author": {
"name": "string",
"organization": "string"
},
"images": ["string"],
"paragraphs": ["string"]
}
```
### Field-by-Field Breakdown
The URL is the canonical link for the page, typically found in the HTML `head` tag under a `link` element with `rel="canonical"`.
**HTML Structure:**
```html theme={"system"}
```
**Parser:**
```json theme={"system"}
{
"type": "terminal",
"description": "Extracts the canonical URL from the page's head section",
"selector": {
"type": "css",
"css_selector": "link[rel='canonical']"
},
"extractor": {
"type": "attr",
"attr": "href"
}
}
```
**Explanation:**
* **Selector:** `link[rel='canonical']` selects the first link element with `rel="canonical"`
* **Extractor:** `attr` with `href` extracts the URL from the href attribute
The article title is contained in an `h1` element within a headline block.
**HTML Structure:**
```html theme={"system"}
Three-legged cat 'brings town together'
```
**Parser:**
```json theme={"system"}
{
"type": "terminal",
"description": "Main headline of the article",
"selector": {
"type": "css",
"css_selector": "div[data-component='headline-block'] h1"
},
"extractor": {
"type": "text"
}
}
```
**Explanation:**
* **Selector:** `div[data-component='headline-block'] h1` targets the h1 inside the headline block
* **Extractor:** `text` extracts the text content from the element
The publication date is in a `time` element and needs to be formatted to ISO format.
**HTML Structure:**
```html theme={"system"}
```
**Parser:**
```json theme={"system"}
{
"type": "terminal",
"description": "Publication date in ISO format",
"selector": {
"type": "css",
"css_selector": "div[data-testid='byline-new'] time"
},
"extractor": {
"type": "text",
"post_processor": {
"type": "date"
}
}
}
```
**Explanation:**
* **Selector:** `div[data-testid='byline-new'] time` targets the time element
* **Extractor:** `text` with `date` post-processor converts "29 July 2024" to "2024-07-29T00:00:00"
This field requires JavaScript rendering. Add render options to your API request to wait for this element.
The author information contains both name and organization, requiring a nested schema parser.
**HTML Structure:**
```html theme={"system"}
Martin Heath
BBC News, Northamptonshire
```
**Parser:**
```json theme={"system"}
{
"type": "schema",
"description": "Author information with name and organization",
"selector": {
"type": "css",
"css_selector": "div[data-testid='byline-new-contributors']"
},
"fields": {
"name": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "span[class]"
},
"extractor": {
"type": "text"
}
},
"organization": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "span:not([class])"
},
"extractor": {
"type": "text"
}
}
}
}
```
**Explanation:**
* **Schema Parser:** Returns a nested object with multiple fields
* **Selector Nesting:** Parent selector scopes child selectors to the byline-new-contributors div
* **Name Selector:** `span[class]` selects spans with a class attribute
* **Organization Selector:** `span:not([class])` selects spans without a class attribute
Extract all image URLs from the article, converting relative URLs to absolute.
**HTML Structure:**
```html theme={"system"}
```
**Parser:**
```json theme={"system"}
{
"type": "terminal_list",
"description": "All article images with absolute URLs",
"selector": {
"type": "css",
"css_selector": "article img"
},
"extractor": {
"type": "attr",
"attr": "src",
"post_processor": {
"type": "url"
}
}
}
```
**Explanation:**
* **terminal\_list:** Returns an array of values instead of a single value
* **Selector:** `article img` selects all img elements within article
* **Extractor:** `attr` with `src` gets the image source
* **Post-processor:** `url` converts relative URLs to absolute (e.g., `/news/...` → `https://www.bbc.com/news/...`)
Extract all article paragraphs as an array of strings.
**HTML Structure:**
```html theme={"system"}
A three-legged cat has captured a town's imagination...
The people of Daventry, Northamptonshire, love taking photographs...
```
**Parser:**
```json theme={"system"}
{
"type": "terminal_list",
"description": "Article content paragraphs",
"selector": {
"type": "css",
"css_selector": "article div[data-component='text-block'] p"
},
"extractor": {
"type": "text"
}
}
```
**Explanation:**
* **terminal\_list:** Returns an array of paragraph texts
* **Selector:** `article div[data-component='text-block'] p` selects all p elements in text blocks
* **Extractor:** `text` extracts the text content from each paragraph
### Complete Parser
```json theme={"system"}
{
"type": "schema",
"description": "Parses a BBC news article into structured data",
"fields": {
"url": {
"type": "terminal",
"description": "Canonical URL of the article",
"selector": {
"type": "css",
"css_selector": "link[rel='canonical']"
},
"extractor": {
"type": "attr",
"attr": "href"
}
},
"title": {
"type": "terminal",
"description": "Main headline of the article",
"selector": {
"type": "css",
"css_selector": "div[data-component='headline-block'] h1"
},
"extractor": {
"type": "text"
}
},
"date": {
"type": "terminal",
"description": "Publication date in ISO format",
"selector": {
"type": "css",
"css_selector": "div[data-testid='byline-new'] time"
},
"extractor": {
"type": "text",
"post_processor": {
"type": "date"
}
}
},
"author": {
"type": "schema",
"description": "Author information",
"selector": {
"type": "css",
"css_selector": "div[data-testid='byline-new-contributors']"
},
"fields": {
"name": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "span[class]"
},
"extractor": {
"type": "text"
}
},
"organization": {
"type": "terminal",
"selector": {
"type": "css",
"css_selector": "span:not([class])"
},
"extractor": {
"type": "text"
}
}
}
},
"images": {
"type": "terminal_list",
"description": "All article images",
"selector": {
"type": "css",
"css_selector": "article img"
},
"extractor": {
"type": "attr",
"attr": "src",
"post_processor": {
"type": "url"
}
}
},
"paragraphs": {
"type": "terminal_list",
"description": "Article content paragraphs",
"selector": {
"type": "css",
"css_selector": "article div[data-component='text-block'] p"
},
"extractor": {
"type": "text"
}
}
}
}
```
### Example Output
```json theme={"system"}
{
"url": "https://www.bbc.com/news/articles/cervlxymly2o",
"title": "Three-legged cat 'brings town together'",
"date": "2024-07-29T00:00:00",
"author": {
"name": "Martin Heath",
"organization": "BBC News, Northamptonshire"
},
"images": [
"https://ichef.bbci.co.uk/news/480/cpsprodpb/2a87/live/321fae30-4c01-11ef-b2d2-cdb23d5d7c5b.jpg.webp",
"https://ichef.bbci.co.uk/news/480/cpsprodpb/a8c2/live/904194b0-4c01-11ef-b2d2-cdb23d5d7c5b.jpg.webp",
"https://ichef.bbci.co.uk/news/480/cpsprodpb/7579/live/9ecae4f0-4c01-11ef-aebc-6de4d31bf5cd.jpg.webp"
],
"paragraphs": [
"A three-legged cat has captured a town's imagination with his appearances in shops and offices.",
"The people of Daventry, Northamptonshire, love taking photographs of the 14-year-old feline and documenting his travels on social media.",
"Funds have been raised to buy a street sign with his name on it, and souvenir Salem T-shirts could follow."
]
}
```
### API Request Example
```python Python theme={"system"}
from nimble_python import Nimble
nimble = Nimble(api_key="YOUR-API-KEY")
result = nimble.extract.run(
url="https://www.bbc.com/news/articles/cervlxymly2o",
parse=True,
render=True,
browser_actions=[{
"wait_for_element": ["div[data-testid='byline-new'] time"]
}],
parser={
"type": "schema",
"description": "Parses a BBC news article into structured data",
"fields": {
# ... (full parser structure as shown above)
}
}
)
print(result)
```
```typescript TypeScript theme={"system"}
import Nimble from "@nimble-way/nimble-js";
const nimble = new Nimble({ apiKey: "YOUR-API-KEY" });
const result = await nimble.extract.run({
url: "https://www.bbc.com/news/articles/cervlxymly2o",
parse: true,
render: true,
browser_actions: [
{
wait_for_element: ["div[data-testid='byline-new'] time"],
},
],
parser: {
type: "schema",
description: "Parses a BBC news article into structured data",
fields: {
// ... (full parser structure as shown above)
},
},
});
console.log(result);
```
```bash cURL theme={"system"}
curl -X POST 'https://sdk.nimbleway.com/v2/extract' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://www.bbc.com/news/articles/cervlxymly2o",
"parse": true,
"render": true,
"browser_actions": [{
"wait_for_element": ["div[data-testid=\"byline-new\"] time"]
}],
"parser": {
"type": "schema",
"description": "Parses a BBC news article into structured data",
"fields": {
...
}
}
}'
```
This parser can be reused for any BBC news article following the same structure - just change the URL!
### Parsing Embedded JSON from Etsy.com Prodcut Page
This example demonstrates parsing structured data from embedded JSON-LD (Linked Data JSON) within an HTML page. Many websites embed JSON-LD in their HTML to help search engines understand their content - we can leverage this for easier, more reliable parsing.
**What is LD+JSON?** Linked Data JSON is a format for structuring data in a machine-readable way. It's often embedded in webpages using `