# Adding Images Source: https://docs.linkup.so/pages/changelog/addingimages _Released: January 07, 2025_ We're excited to introduce the ability to include images in your search results. This new feature allows you to retrieve relevant images alongside your regular search data. ### How to enable To include images in your search results, set `includeImages` to `true` in your API request. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Who is Barack Obama?", "depth": "standard", "outputType": "searchResults", "includeImages": "true" }' ``` **Example Response** ```json json theme={"system"} { "results": [ { "type": "image", "name": "Barack Obama | Biography, Presidency, & Facts | Britannica.com", "url": "https://cdn.britannica.com/43/172743-138-545C299D/overview-Barack-Obama.jpg" }, { "type": "text", "name": "Barack Obama Biography", "url": "https://www.biography.com/political-figures/barack-obama", "content": "Barack Obama was the 44th president of the United States and the first Black commander-in-chief. He served two terms, from 2009 until 2017." } ] } ``` # Linkup CLI Source: https://docs.linkup.so/pages/changelog/cli _Released: June 2026_ We're introducing the Linkup CLI, a terminal client for running [**Search**](/pages/documentation/endpoints/search/overview), [**Fetch**](/pages/documentation/endpoints/fetch/overview), [**Research**](/pages/documentation/endpoints/research/overview) and [**Tasks**](/pages/documentation/endpoints/tasks/overview) from your shell. Install it from npm: ```shell theme={"system"} npm install -g linkup-search-cli ``` Configure your API key once with `linkup setup`, or set `LINKUP_API_KEY` for scripts and CI. Read the full CLI guide: [CLI](/pages/sdk/cli/cli). ### How to use ```shell theme={"system"} linkup search "What is Microsoft's 2024 revenue?" --depth deep linkup fetch https://example.com --render-js linkup research "State of the semiconductor market in 2026" --wait linkup tasks create --file tasks.json --wait ``` # Dates Filtering Source: https://docs.linkup.so/pages/changelog/datefiltering _Released: March 19, 2024_ We're excited to introduce date filtering capabilities for your search results. This new feature allows you to narrow down your search results to specific time periods, making it easier to find the most relevant and up-to-date information. ### How to enable To filter your search results by date, add the `fromDate` and `toDate` parameters to your API request. You can specify either a start date, end date, or both. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Latest developments in AI", "depth": "standard", "outputType": "searchResults", "fromDate": "2024-01-01", "toDate": "2024-03-19" }' ``` **Example Response** ```json theme={"system"} { "results": [ { "type": "text", "name": "Recent AI Developments", "url": "https://example.com/ai-news", "content": "Latest breakthroughs in artificial intelligence...", "date": "2024-03-15" } ] } ``` The date filtering parameters accept dates in ISO 8601 format (YYYY-MM-DD). You can use either: * `fromDate`: Filter results from this date onwards * `toDate`: Filter results up to this date * Both `fromDate` and `toDate`: Filter results within this date range # Extract Endpoint Source: https://docs.linkup.so/pages/changelog/extract-endpoint _Released: June 2026 (closed beta)_ We're introducing the new [**Extract**](/pages/documentation/endpoints/extract/overview) endpoint (`/extract`), a web data agent that turns a page into structured rows. Given a seed `url` and a natural-language query describing the rows you want, the agent extracts the matching records and returns them as a downloadable file. Asynchronous lifecycle. Optional row `schema` and URL verification. **Extract** is in closed beta and access is granted by request — [request access](mailto:contact@linkup.so?subject=Request%20access%20to%20%2Fextract%20beta) to try it. ### How to use 1. Send a `POST /extract` request with a seed `url` and a `q` describing which rows to extract. The endpoint immediately returns a task identifier and `status` of `"pending"`. 2. Poll `GET /extract/{id}` to retrieve a specific task, or `GET /extract` to list all your extract tasks. 3. While running, `status` is `"pending"` or `"processing"`. Once the task is `"completed"`, read `output.resultUrl` and download the rows. If `"failed"`, inspect `error`. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/extract \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "All engineering team members with their name, role, and profile page", "url": "https://example.com/team", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "role": { "type": "string" }, "profileUrl": { "type": "string", "format": "uri" } }, "required": ["name", "profileUrl"] }, "verifyUrls": true }' ``` ### Parameters | Parameter | Description | | ------------ | -------------------------------------------------------------------------------------------------------- | | `q` | Natural-language query describing which rows to extract and what each row should contain. Required. | | `url` | The seed URL the extract task starts from. Required. | | `schema` | Optional JSON schema describing a single extracted row. When provided, every returned row must match it. | | `verifyUrls` | Whether URLs found in extracted rows are checked for reachability after extraction. Defaults to `false`. | ### Output The extracted rows are **not** returned inline. Once `"completed"`, `output` contains a `resultUrl` — a download link to a newline-delimited JSON (NDJSON) file, one row per line — valid for 24 hours. It also reports `rowsReturned` and `creditsUsed`, the credits used by the task. Read the full reference: [`POST /extract`](/pages/documentation/endpoints/extract/post) · [`GET /extract`](/pages/documentation/endpoints/extract/list) · [`GET /extract/:id`](/pages/documentation/endpoints/extract/get). For operational guidance, see [extract best practices](/pages/documentation/endpoints/extract/best-practices). # Fast Depth Source: https://docs.linkup.so/pages/changelog/fast-depth _Released: February 2026_ We're excited to introduce `"fast"`, a new `depth` value for the Linkup [**Search**](/pages/documentation/endpoints/search/overview) endpoint. `"fast"` is our sub-second search mode, optimized to provide accurate results and snippets in the lowest possible time. `"fast"` works best with focused queries, where you're looking for one specific piece of information. ### What's new `"fast"` uses a simpler, more direct search pipeline than `"standard"` or `"deep"`. The key difference is scope: * **`"fast"`**: one piece of information, one direct answer. Best for short, focused prompts. Only search results and relevant content snippets are returned. * **`"standard"`**: best to handle several topics, data sources (general web search, financial data, LinkedIn public data), or retrieve fully scraped content of a URL provided in the query. This makes `"fast"` ideal for **conversational use cases**, real-time lookups, and high-volume pipelines where latency is critical. ### How to use A simple rule of thumb: if your prompt is short and you're looking for **one specific thing**, use `"fast"`. If your prompt is longer or spans multiple topics, use `"standard"`. | Use `"fast"` | Use `"standard"` | | ------------------------------- | ------------------------------------------------------------- | | "Who is the CEO of OpenAI?" | "Find the pricing, features, and customer reviews for Notion" | | "Current EUR/USD exchange rate" | "Current EUR/USD exchange rate and analysts analysis" | | "What is Linkup's website?" | "What is Linkup's website and LinkedIn URL" | Set `depth` to `"fast"` in your API request: ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "weather in Paris today", "depth": "fast", "outputType": "sourcedAnswer" }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") response = client.search( query="weather in Paris today", depth="fast", output_type="sourcedAnswer" ) print(response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); client.search({ query: "weather in Paris today", depth: "fast", outputType: "sourcedAnswer", }).then(console.log); ``` ### When to use each depth | Depth | Best for | | ------------ | --------------------------------------------------------------------------------- | | `"fast"` | Simple, focused queries: one piece of information, low latency | | `"standard"` | General queries, multiple topics or sources | | `"deep"` | Sequential queries: search for info, then scrape or search again based on results | ### Pricing `"fast"` depth costs **\$0.005 per call** (with `outputType: "searchResults"`), the same as `"standard"`. For more details, see [Concepts](/pages/documentation/endpoints/search/overview). # Fetch Endpoint Source: https://docs.linkup.so/pages/changelog/fetch-endpoint _Released: September 12, 2025_ We're excited to introduce the new [**Fetch**](/pages/documentation/endpoints/fetch/overview) endpoint (`/fetch`), which allows you to retrieve the raw HTML or markdown content of any webpage with optional JavaScript rendering capabilities. ### How to use The **Fetch** endpoint accepts a `url` and returns the webpage content in your preferred format. You can choose whether to render JavaScript or retrieve the static HTML. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/fetch \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "url": "https://example.com/page", "outputFormat": "html", "renderJS": false }' ``` **Example Response** ```json json theme={"system"} { "url": "https://example.com/page", "content": "Example Page

Example Domain

This domain is for use in illustrative examples in documents...

", "outputFormat": "html", "timestamp": "2025-09-12T10:30:00Z" } ``` ### Key features * **Flexible content retrieval**: get webpage content as raw HTML or clean markdown. * **JavaScript rendering**: optional JavaScript execution for dynamic content. * **Direct URL access**: fetch content from any publicly accessible webpage. * **Clean output**: structured response with metadata and content. This endpoint is perfect for content extraction, web scraping, and building applications that need to process webpage data programmatically. # Fetch Pro mode Source: https://docs.linkup.so/pages/changelog/fetch-pro-mode _Released: August 2026_ We're introducing `"pro"`, a new `mode` value for the Linkup [**Fetch**](/pages/documentation/endpoints/fetch/overview) endpoint. `"pro"` delivers significantly higher success rates on hard-to-retrieve pages. ### What's new `mode` is independent of `renderJs`: * **`mode`**: how the page is accessed. `"standard"` (default) for regular pages. `"pro"` for hard-to-retrieve pages. * **`renderJs`**: whether JavaScript is executed before extraction. ### How to use Set `mode` to `"pro"` in your API request: ```shell curl theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hard-to-retrieve-page", "mode": "pro", "renderJs": false }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") response = client.fetch( url="https://example.com/hard-to-retrieve-page", mode="pro", render_js=False, ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const response = await client.fetch({ url: 'https://example.com/hard-to-retrieve-page', mode: 'pro', renderJs: false, }); console.log(response); ``` ### Pricing | `renderJs` | `mode` = `"standard"` | `mode` = `"pro"` | | ---------- | --------------------- | ---------------- | | `false` | \$0.001 per call | \$0.005 per call | | `true` | \$0.005 per call | \$0.01 per call | For when to choose `"pro"` versus `"standard"`, see [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices). # Fetch structured output Source: https://docs.linkup.so/pages/changelog/fetch-structured-output _Released: August 2026_ [**Fetch**](/pages/documentation/endpoints/fetch/overview) can now return typed JSON from a known URL on the same `/fetch` call. Pass a JSON Schema in `schema` and the response keeps the existing markdown while adding `data`. Optional `instructions` steer extraction for rules the schema cannot express. Default **Fetch** is unchanged: URL in, markdown out. ### What's new * `schema` — JSON Schema of `type` `"object"`. Turns structured output on. Field `description`s tell the model what to look for. * `instructions` — optional. Requires `schema`. Maximum 4,000 characters. * `data` — present in the response only when `schema` is set. Fields with no grounded value are omitted, even if marked `required`. * Same call otherwise: `mode`, `renderJs`, raw content, and images are independent. **Fetch** still reads this URL only. It does not follow links. For many rows from a listing page, use [**Extract**](/pages/documentation/endpoints/extract/overview). Search structured output stays on `structuredOutputSchema`. ### How to use ```shell curl theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.linkup.so/careers", "schema": { "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position" }, "city": { "type": "string", "description": "The city where the job is located" } } } } } }, "instructions": "If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city." }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") response = client.fetch( url="https://www.linkup.so/careers", schema={ "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position", }, "city": { "type": "string", "description": "The city where the job is located", }, }, }, } }, }, instructions="If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city.", ) print(response.data) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const response = await client.fetch({ url: 'https://www.linkup.so/careers', schema: { type: 'object', properties: { jobs: { type: 'array', items: { type: 'object', properties: { position: { type: 'string', description: 'The job title or position', }, city: { type: 'string', description: 'The city where the job is located', }, }, }, }, }, }, instructions: 'If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city.', }); console.log(response.data); ``` If your current SDK release does not accept `schema` or `instructions`, send the same JSON body to `POST /v1/fetch`. ### Pricing Adding a `schema` adds a flat **\$0.001** on top of any combination of `mode` and `renderJs`. | `mode` | `renderJs` | Markdown only | With `schema` | | ------------ | ---------- | ---------------- | ---------------- | | `"standard"` | `false` | \$0.001 per call | \$0.002 per call | | `"standard"` | `true` | \$0.005 per call | \$0.006 per call | | `"pro"` | `false` | \$0.005 per call | \$0.006 per call | | `"pro"` | `true` | \$0.01 per call | \$0.011 per call | For schema design, missing-field behavior, and when to use **Fetch** versus **Extract** or **Search**, see [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices). # Linkup Flash Source: https://docs.linkup.so/pages/changelog/flash-depth _Released: September 2026_ We're releasing **Linkup Flash**, a new `depth` value for the [**Search**](/pages/documentation/endpoints/search/overview) endpoint. `"flash"` is our lowest-latency search mode: ranked sources and snippets in under 200 ms. ### What's new `"flash"` uses a more direct search pipeline than `"fast"`, `"standard"`, or `"deep"`: * **`"flash"`**: lowest latency. One piece of information, one direct answer. No query reinterpretation, no scraping. * **`"fast"`**: higher-quality one-shot retrieval in about a second. Still no agentic planning. * **`"standard"`**: single-iteration agentic search for queries that span several topics or sources. * **`"deep"`**: multi-iteration agentic search-and-scrape. ### How to use Set `depth` to `"flash"` in your API request: ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "What changed in the latest EU AI Act guidance?", "depth": "flash", "outputType": "searchResults" }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") response = client.search( query="What changed in the latest EU AI Act guidance?", depth="flash", output_type="searchResults", ) print(response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const response = await client.search({ query: "What changed in the latest EU AI Act guidance?", depth: "flash", outputType: "searchResults", }); console.log(response); ``` `"flash"` and `"fast"` pass the query to the index as-is. Keep prompts short and keyword-shaped. For instruction-style prompts (scrape this URL, then search again), use `"standard"` or `"deep"`. ### When to use each depth | Depth | Best for | Latency | | ------------ | ----------------------------------------------------------------- | -------- | | `"flash"` | Simple, focused queries: one piece of information, lowest latency | \<200 ms | | `"fast"` | Higher-quality one-shot lookups when \~1s is acceptable | \~1s | | `"standard"` | Instruction-style retrieval, several topics or one provided URL | 1–3s | | `"deep"` | Sequential search-then-scrape chains | 5–30s | ### Pricing `"flash"` costs **\$0.005 per call** with `outputType: "searchResults"`, the same as `"fast"` and `"standard"`. `"sourcedAnswer"` and `"structured"` are \$0.006 per call. For more details, see [Search overview](/pages/documentation/endpoints/search/overview). # maxResults parameter Source: https://docs.linkup.so/pages/changelog/max-results _Released: November 14, 2025_ We're excited to introduce the new `maxResults` parameter for the [**Search**](/pages/documentation/endpoints/search/overview) endpoint, which allows you to specify the maximum number of search results returned in a single API call. This feature is designed to give you more control over the size of the context passed to downstream LLMs. ### How to use To specify a maximum number of search results, add the `maxResults` parameter to your API request. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Latest developments in AI", "depth": "standard", "outputType": "sourcedAnswer", "maxResults": 10 }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """Latest developments in AI""" search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer", max_results=10, ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = `Latest developments in AI`; client.search({ query, depth: "standard", outputType: "sourcedAnswer", maxResults: 10 }).then(console.log); ``` # Research Endpoint Source: https://docs.linkup.so/pages/changelog/research-endpoint _Released: May 2026_ We're introducing the new [**Research**](/pages/documentation/endpoints/research/overview) endpoint (`/research`), an autonomous research agent that investigates the web to handle questions a single search query cannot resolve. The agent plans its retrieval, gathers evidence from multiple sources in parallel, iterates through investigation, and returns a sourced response with inline citations. Three modes, four reasoning depths, two output types. Asynchronous lifecycle. ### How to use 1. Send a `POST /research` request with a natural-language question and the parameters that pin the investigation. The endpoint immediately returns a task identifier and `status` of `"pending"`. 2. Poll `GET /research/{id}` to retrieve a specific task, or `GET /research` to list all your research tasks. 3. While running, `status` is `"pending"` or `"processing"`. Once the task is `"completed"`, read the result in `output`. If `"failed"`, inspect `error`. **Example Request** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/research \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", "mode": "investigate", "reasoningDepth": "L", "outputType": "sourcedAnswer" }' ``` ### Modes | Mode | Description | | --------------- | -------------------------------------------------------------------------------------------------------- | | `"answer"` | A precise, evidence-backed answer to a question with a definitive solution. Lowest latency of the three. | | `"investigate"` | A focused report on a single defined subject, examining each angle and verifying claims. | | `"research"` | A structured report organized by theme, covering many topics or entities in parallel. Highest latency. | If `mode` is not provided, the agent automatically classifies the question and selects one of the three modes for the request. ### Reasoning depth `reasoningDepth` controls thoroughness. Higher depths have more compute budget: they consult more sources, perform more iterations and cross-checking, produce longer outputs, and take longer to run. | Depth | Order-of-magnitude latency | | ------ | -------------------------- | | `"S"` | 2–5 minutes | | `"M"` | 3–7 minutes | | `"L"` | 5–10 minutes | | `"XL"` | 10–20 minutes | Omitting the parameter defaults to `"L"`. ### Output types | `outputType` | Description | | ----------------- | --------------------------------------------------------- | | `"sourcedAnswer"` | Natural-language answer with inline citations. | | `"structured"` | JSON matching a caller-provided `structuredOutputSchema`. | ### Billing The **Research** endpoint is billed per call at a flat rate per `reasoningDepth`, regardless of `mode`. See [pricing](/pages/documentation/platform/pricing#research-endpoint). Read the full reference: [`POST /research`](/pages/documentation/endpoints/research/post) · [`GET /research`](/pages/documentation/endpoints/research/list) · [`GET /research/:id`](/pages/documentation/endpoints/research/get). For operational guidance, see [research best practices](/pages/documentation/endpoints/research/best-practices). # Tasks Endpoint Source: https://docs.linkup.so/pages/changelog/tasks-endpoint _Released: April 2026_ We're excited to introduce the new [**Tasks**](/pages/documentation/endpoints/tasks/overview) endpoint (`/tasks`), which allows you to run Linkup's core endpoints in batch and asynchronously. Run any combination of [**Search**](/pages/documentation/endpoints/search/reference), [**Fetch**](/pages/documentation/endpoints/fetch/reference), and [**Research**](/pages/documentation/endpoints/research/post) calls in a single batch, with the exact same parameters as their synchronous versions, at no extra cost. ### How to use 1. Send a `POST /tasks` request with an array of `{ type, input }` objects (up to 100 per call). The endpoint immediately returns the tasks' identifiers, before the work is completed. 2. Poll `GET /tasks/{id}` to retrieve a specific task, or `GET /tasks` to list all your tasks. 3. While running, `status` is `"pending"` or `"processing"`. Once the task is `"completed"`, read the result in `output`. If `"failed"`, inspect `error`. **Example Request** ```shell curl expandable theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/tasks \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '[ { "type": "search", "input": { "q": "What is Microsoft'\''s 2024 revenue?", "depth": "deep", "outputType": "sourcedAnswer", "includeDomains": ["microsoft.com", "agolution.com"], "excludeDomains": ["wikipedia.org"] } }, { "type": "fetch", "input": { "url": "https://docs.linkup.so", "renderJs": false, "includeRawHtml": false, "extractImages": false } }, { "type": "research", "input": { "q": "What is Microsoft'\''s 2024 revenue?", "outputType": "sourcedAnswer", "includeImages": true, "includeDomains": ["microsoft.com", "agolution.com"], "excludeDomains": ["wikipedia.org"] } } ]' ``` ### When to use `/tasks` * **Bulk workloads** — CRM enrichment, backfilling, batch research over hundreds of queries. * **Long-running jobs** — submit the request, then poll for the result instead of waiting for the response synchronously. * **Scheduled pipelines** — kick off a nightly batch, retrieve results in the morning. For interactive, single-shot calls (chat UIs, agent steps), keep using the synchronous **Search**, **Fetch**, and **Research** endpoints directly. ### Billing Each task is billed exactly like a direct **Search**, **Fetch**, or **Research** call. Read the full reference: [`POST /tasks`](/pages/documentation/endpoints/tasks/post) · [`GET /tasks`](/pages/documentation/endpoints/tasks/list) · [`GET /tasks/:id`](/pages/documentation/endpoints/tasks/get). # URL Filtering Source: https://docs.linkup.so/pages/changelog/urlfiltering _Released: jun 20, 2025_ We're excited to introduce URL filtering capabilities for your search results. This new feature allows you to include or exclude specific URLs in your search results, making it easier to find the most relevant information. ### How to enable To filter your search results by URLs or domains, add the `excludeDomains` or `includeDomains` parameters to your API request. **Example request with `excludeDomains`** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Latest developments in AI", "depth": "standard", "outputType": "sourcedAnswer", "excludeDomains": ["apple.com"] }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """Latest developments in AI""" search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer", exclude_domains=["apple.com"] ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = `Latest developments in AI`; client.search({ query, depth: "standard", outputType: "sourcedAnswer", excludeDomains: ["apple.com"] }).then(console.log); ``` **Example Response** ```json theme={"system"} { "answer": "AI agents are the dominant innovation in 2025, focusing on autonomous AI programs that can independently complete projects...", "sources": [ { "name": "AI Agents in 2025: Expectations vs. Reality | IBM", "snippet": "For 2025, the dominant innovation narrative is the AI agent...", "url": "https://www.ibm.com/think/insights/ai-agents-2025-expectations-vs-reality" } ] } ``` **Example request with `includeDomains`** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "How to build MCP server", "depth": "standard", "outputType": "sourcedAnswer", "includeDomains": ["dev.to"] }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """How to build MCP server?""" search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer", include_domains=["dev.to"] ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = `How to build MCP server?`; client.search({ query, depth: "standard", outputType: "sourcedAnswer", includeDomains: ["dev.to"] }).then(console.log); ``` **Example Response** ```json theme={"system"} { "answer": "Steps to build an MCP server in 2025:[...]", "sources": [ { "name": "🚀Top 10 MCP Servers for 2025 (Yes, GitHub’s Included!) - DEV Community", "snippet": "Enter the Model Context Protocol ... ", "url": "https://dev.to/fallon_jimmy/top-10-mcp-servers-for-2025-yes-githubs-included-15jg" } ] } ``` The URLs filtering parameters accept an Array of strings. You can use: * `excludeDomains`: Don't use those Domains/URLs in the results * `includeDomains`: Only use those Domains/URLs in the results If you use `excludeDomains` and `includeDomains`, the results will logically only include Domains/URLs that match the inclusion. # USD Pricing Source: https://docs.linkup.so/pages/changelog/usd-pricing _Released: May 2026_ Linkup API pricing is now denominated in **USD**, with a 1:1 conversion across per-call rates and existing balances — same numbers, new currency symbol. The change is net-positive for callers: the monthly free top-up for eligible accounts quadruples from \$5 to \$20, bulk top-ups now earn tiered bonuses of up to 20%, and because the dollar trades below the euro, every call is a touch cheaper in real terms. No migration required on your side. Two structural notes on per-call pricing: * **Fetch and Research keep their numbers** — only the currency symbol moves (€ → \$, 1:1). * **Search now prices on `outputType` as well as `depth`.** Raw `"searchResults"` calls hold their previous nominal cost; `"sourcedAnswer"` and `"structured"` carry a small LLM premium for the composition step. Most callers consume `"searchResults"` directly and see no change. ### New search pricing | `depth` | `outputType` | Cost | | ----------------------- | ---------------------------------- | ------- | | `"fast"` / `"standard"` | `"searchResults"` | \$0.005 | | `"fast"` / `"standard"` | `"sourcedAnswer"` / `"structured"` | \$0.006 | | `"deep"` | `"searchResults"` | \$0.05 | | `"deep"` | `"sourcedAnswer"` / `"structured"` | \$0.055 | ### Fetch and Research (no change in amounts) | Endpoint | Variant | Cost | | ----------- | ---------------------- | ------- | | `/fetch` | `renderJs: false` | \$0.001 | | `/fetch` | `renderJs: true` | \$0.005 | | `/research` | `reasoningDepth: "S"` | \$0.25 | | `/research` | `reasoningDepth: "M"` | \$0.50 | | `/research` | `reasoningDepth: "L"` | \$1.50 | | `/research` | `reasoningDepth: "XL"` | \$2.50 | ### Account balances EUR credit balances have been converted to USD at parity, so existing balances continue to fund the same number of calls. For accounts created with a professional email address, the monthly free top-up is now \$20. Bulk top-ups earn extra credits at three thresholds: \$1,000 (10%), \$5,000 (15%), and \$10,000 (20%). ### x402 The [x402 protocol](/pages/documentation/platform/x402) was already denominated in USD. Per-request costs follow the new rates above; the \$0.01 minimum charge per request is unchanged. See the [pricing page](/pages/documentation/platform/pricing) for the full breakdown. # x402 Payment Protocol Source: https://docs.linkup.so/pages/changelog/x402 _Released: March 2026_ Linkup now supports [x402](https://x402.org), an open payment protocol that lets you pay per request in USDC on the Base network, with no account or API key required. ### What's new * **Pay per request.** Send USDC directly from your wallet, no sign-up needed. * **All endpoints supported:** `/v1/search`, `/v1/fetch`, and `/v1/research`. * **Standard HTTP flow.** Get a `402` response, sign with your wallet, resend with the payment header. * **On-chain settlement.** Every payment is verified and settled on Base. ### Get started See the [x402 documentation](/pages/documentation/platform/x402) for pricing, a quickstart example, and headers reference. # /credits/balance Source: https://docs.linkup.so/pages/documentation/endpoints/account/balance https://api.linkup.so/v1/openapi.json GET /v1/credits/balance The `/credits/balance` endpoint allows you to retrieve your current credits balance. Create a Linkup account for free to get your API key. The `/credits/balance` endpoint returns your current credit balance. # Extract best practices Source: https://docs.linkup.so/pages/documentation/endpoints/extract/best-practices Query phrasing, schema design, URL verification, polling, and failure handling for /extract. This page covers how to phrase an extract query, design a row schema, and poll and handle failures on the **Extract** endpoint. **Extract** is in **closed beta**. Access is limited to a small group of users while we refine it — [request access](mailto:contact@linkup.so?subject=Request%20access%20to%20%2Fextract%20beta). Parameters, behavior, and response shape may change. ## Choosing a seed URL Extract starts from the single `url` you provide. Point it at the page that already lists the records you want — a team page, a product catalog, a directory, a search results page — rather than a site's homepage. The more directly the seed page contains the rows, the more reliable the extraction. ## Query phrasing `q` describes both **which rows** to extract and **what each row should contain**. Name the unit of repetition and the fields explicitly: ```text theme={"system"} Each engineering team member, with their full name, job title, and the URL of their individual profile page. ``` Useful dimensions to specify include: * the entity that defines one row (a person, a product, a job posting), * the fields each row must contain, * any filter that limits which records qualify, and * how to handle missing fields. ## Designing a row schema When you need a guaranteed shape, pass an explicit `schema`. It describes **one row**, and every returned row must match it. * Keep the schema **flat**. Primitive fields (`string`, `number`, `boolean`) are most reliable. * Mark only genuinely mandatory fields as `required`. Over-constraining drops otherwise-valid rows. * Use `"format": "uri"` for link fields so they can be validated with `verifyUrls`. * Reshape into nested structures client-side after extraction rather than requesting deep nesting. If `schema` is omitted, the agent infers the row shape from `q`. Provide a schema whenever downstream code depends on stable field names and types. ## Verifying URLs Set `verifyUrls` to `true` when extracted links must resolve — for example, when building a dataset of working profile or product pages. Verification checks each URL for reachability after extraction, which adds latency. Leave it at the default `false` when you only need the raw values. ## Reading the result The result is delivered as a downloadable NDJSON file, not inline: * `output.resultUrl` is valid for **24 hours**. Download and persist the rows promptly. * The file has **one JSON object per line**; parse it line by line rather than as a single JSON document. * `output.rowsReturned` tells you how many rows to expect. * `output.creditsUsed` gives the credits used by the task. ## Polling Polling loops are a common source of integration errors. Recommended defaults: * **Interval**: poll roughly every 30 seconds for long-running tasks. * **Maximum poll rate**: 1 request per second. Higher rates trigger rate limits without reducing time-to-completion. ## Failure handling No credit is deducted for failed tasks or tasks that return no result. Retries are unrestricted. This policy is consistent with the other endpoints. See [errors](/pages/documentation/platform/errors). ## Resources * [Extract overview](/pages/documentation/endpoints/extract/overview) * [Extract for AI agents](/pages/documentation/endpoints/extract/for-agents) * [Structured output tutorial](/pages/documentation/tutorials/structured-output-guide) * [Errors](/pages/documentation/platform/errors) and [pricing](/pages/documentation/platform/pricing) # Extract for AI agents Source: https://docs.linkup.so/pages/documentation/endpoints/extract/for-agents Self-contained /extract integration prompt for coding agents that need structured data extraction from the web (closed beta). This page is structured for direct use as integration context for a coding agent, or as a function-calling tool definition. Operational guidance is repeated inline so the page is self-contained. *** # Linkup `/extract` integration guide You are integrating the **Linkup `/extract` API**: a closed-beta web data agent (access is granted by request) that starts from a seed URL and returns structured rows of data. Given a `url` and a natural-language query `q` describing the rows to collect, the agent extracts the matching records and writes them to a downloadable NDJSON file. Use it to turn listing pages (teams, catalogs, directories, job boards) into tables of consistent records. Two required fields (`q`, `url`), an optional row `schema`, and an optional `verifyUrls` flag. Async lifecycle: `POST` returns the task (with its id) immediately; poll `GET /extract/:id` until `status` is `"completed"`, then download `output.resultUrl` (the rows are not in the API response). ## When to use it Use `/extract` when the data is **repeated records on a known page** and you want them as structured rows. If you only need one page's content as markdown, or typed JSON from that one page, use `/fetch` (pass `schema` for JSON). If the value is multi-source synthesis with citations, use `/research`. Other endpoints in the API: * [**Search**](/pages/documentation/endpoints/search/overview) (`/search`): synchronous web search. * [**Fetch**](/pages/documentation/endpoints/fetch/overview) (`/fetch`): one known URL as markdown, or typed JSON from that page via `schema`. * [**Research**](/pages/documentation/endpoints/research/overview) (`/research`): autonomous multi-source research agent. * [**Tasks**](/pages/documentation/endpoints/tasks/overview) (`/tasks`): asynchronous batch wrapper. ## Setup ```bash theme={"system"} export LINKUP_API_KEY="your-api-key" ``` ## Example (curl; adapt to the project's HTTP client) ```bash theme={"system"} # /extract is async: POST returns immediately with an id. Poll until completed, # then download the NDJSON result from output.resultUrl (valid 24h). # 1. Submit curl -X POST "https://api.linkup.so/v1/extract" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "q": "All engineering team members with their name, role, and profile page", "url": "https://example.com/team", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "role": { "type": "string" }, "profileUrl": { "type": "string", "format": "uri" } }, "required": ["name", "profileUrl"] }, "verifyUrls": true }' # 2. Poll (initial 2s, back off to 10s, max 1 request/second) curl "https://api.linkup.so/v1/extract/" \ -H "Authorization: Bearer $LINKUP_API_KEY" # 3. Download rows once status == "completed" curl "" ``` ## Tool definition (OpenAI function-calling format) Remove the `"type": "function"` envelope and rename `parameters` to `input_schema` for the Anthropic format. Note that this tool is **async**: the handler should poll on the model's behalf and return the completed result (or the downloaded rows), not the task id. ```json theme={"system"} { "type": "function", "function": { "name": "linkup_extract", "description": "Extracts structured rows of data from a web page. Provide a seed URL and a natural-language description of the rows to collect; returns the rows as an NDJSON file. Use for repeated records on a known page (team members, products, jobs, directory entries). Async: the handler should poll until completion and download the result.", "parameters": { "type": "object", "properties": { "q": { "type": "string", "description": "Natural-language query describing which rows to extract and what each row should contain. Name the unit of repetition (one person, one product, one job) and the fields each row must include." }, "url": { "type": "string", "description": "The seed URL the extract task starts from. Point it at the page that already lists the records, not the site homepage." }, "schema": { "type": "object", "description": "Optional JSON schema describing a single extracted row. When provided, every returned row must match it. Keep it flat; use format 'uri' for link fields. Omit to let the agent infer the shape from q and the data structure on the website." }, "verifyUrls": { "type": "boolean", "description": "Whether URLs found in extracted rows are checked for reachability after extraction. Adds latency. Defaults to false.", "default": false } }, "required": ["q", "url"] } } } ``` ## Operational guidance (inline) ### Seed URL Point `url` at the page that already contains the records (listing, directory, catalog), not a homepage. The more directly the seed page lists the rows, the more reliable the extraction. ### Query phrasing `q` should name the entity that defines one row and the fields each row must contain, plus any filter limiting which records qualify. ### Schema design Flat schemas with primitive fields are most reliable. Mark only mandatory fields as `required`; over-constraining drops valid rows. Reshape into nested structures client-side after extraction. ### URL verification Set `verifyUrls` to `true` only when extracted links must resolve; it adds latency. ### Reading the result `output.resultUrl` points to an NDJSON file (one JSON object per line), valid for 24 hours. Download and persist it promptly. `output.rowsReturned` gives the row count. `output.creditsUsed` gives the credits used by the task. ### Polling * Interval: poll roughly every 30 seconds for long-running tasks. * Maximum poll rate: 1 request per second. ### Constraints * Parse the result as NDJSON (line by line), not as a single JSON document. * The `resultUrl` expires after 24 hours. * Polling without backoff triggers rate limits without reducing time-to-completion. ## Beta status The **Extract** endpoint is currently in **beta**. Behavior, parameters, and response shape may change. ## Links * [Extract overview](/pages/documentation/endpoints/extract/overview) * [Extract best practices](/pages/documentation/endpoints/extract/best-practices) * [API reference: POST /extract](/pages/documentation/endpoints/extract/post) * [API reference: GET /extract](/pages/documentation/endpoints/extract/list) * [API reference: GET /extract/:id](/pages/documentation/endpoints/extract/get) * [OpenAPI spec](https://api.linkup.so/v1/openapi.json) # /extract/:id Source: https://docs.linkup.so/pages/documentation/endpoints/extract/get https://api.linkup.so/v1/openapi.json GET /v1/extract/{id} [BETA] The `GET /extract/:id` method, with extract task identifier, retrieves a specific extract task information. Create a Linkup account for free to get your API key. This endpoint only retrieves data from an existing extract task. It does not create a new extract task. To create a new task, use [`POST /extract`](/pages/documentation/endpoints/extract/post). # /extract Source: https://docs.linkup.so/pages/documentation/endpoints/extract/list https://api.linkup.so/v1/openapi.json GET /v1/extract [BETA] The `GET /extract` method, without extract task identifier, lists all created extract tasks for the authenticated organization, with pagination. Create a Linkup account for free to get your API key. This endpoint only retrieves data from existing extract tasks. It does not create a new extract task. To create a new task, use [`POST /extract`](/pages/documentation/endpoints/extract/post). # Extract overview Source: https://docs.linkup.so/pages/documentation/endpoints/extract/overview Closed-beta agent that turns a web page into structured rows of data, delivered as a downloadable NDJSON file. **Extract** is in **closed beta**. Access is limited to a small group of users while we refine it — [request access](mailto:contact@linkup.so?subject=Request%20access%20to%20%2Fextract%20beta). Parameters, behavior, and response shape may change. **Extract** is Linkup's web data agent. Given a seed `url` and a natural-language description of the rows you want, it extracts the matching records and returns them as structured rows. Use cases include: * turning a listing page (team, catalog, directory) into a table of records, * pulling repeated entities (people, products, jobs) with consistent fields, and * collecting links from a page and verifying they resolve. The agent starts from the `url` you provide, extracts the rows described by `q`, and writes the result to a downloadable NDJSON file. For a single known URL where you want one JSON object (not a table of rows, and without following links), use [**Fetch**](/pages/documentation/endpoints/fetch/overview) with `schema` instead. ## Parameters | Parameter | Type | Default | Description | | ------------ | --------- | ------------ | ----------------------------------------------------------------------------------------------- | | `q` | `string` | *(required)* | Natural-language query describing which rows to extract and what each row should contain. | | `url` | `string` | *(required)* | The seed URL the extract task starts from. | | `schema` | `object` | `null` | JSON schema describing a single extracted row. When provided, every returned row must match it. | | `verifyUrls` | `boolean` | `false` | Whether URLs found in extracted rows are checked for reachability after extraction. | ### The `schema` parameter When `schema` is omitted, the agent infers the shape of each row from `q` and from the data structure on the website. Pass an explicit `schema` to pin the fields and types of every row. The schema describes **one row** — the agent applies it to each record it extracts. For example, to extract one row per team member: ```json theme={"system"} { "type": "object", "properties": { "name": { "type": "string", "description": "Full name" }, "role": { "type": "string" }, "profileUrl": { "type": "string", "format": "uri" } }, "required": ["name", "profileUrl"] } ``` ### The `verifyUrls` parameter When `verifyUrls` is `true`, any URL found in an extracted row is checked for reachability after extraction. This adds latency but filters out dead links. Leave it `false` when you only need the raw extracted values. ## Output Extract does **not** return the extracted rows in the API response. The `output` object contains a `resultUrl` — a download link to a separate file. You must make a second request to that URL to get the data. The link is valid for **24 hours**. Once the task is `"completed"`, `output` holds a link to a newline-delimited JSON (NDJSON) file — one extracted row per line — rather than the rows themselves: | Field | Type | Description | | -------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | | `creditsUsed` | `number` | Credits used by this extract task. | | `resultUrl` | `string` | Download link for the extracted rows as an NDJSON file. **The rows live here, not in the API response.** Valid for 24 hours. | | `rowsReturned` | `integer` | Number of rows in the downloadable file. | To get the data: poll `GET /v1/extract/:id` until `status` is `"completed"`, read `output.resultUrl`, then make a separate `GET` request to that URL to download and parse the NDJSON file (one JSON object per line). ## Pricing **Extract** uses variable pricing: cost scales with crawl complexity — page size, number of rows extracted, pagination depth, and whether `verifyUrls` is enabled. Most completed tasks fall in the **\$2–10** range. The exact charge appears as `creditsUsed` in the task `output` once `status` is `"completed"`. No credit is deducted for failed tasks. A minimum account balance of **\$10** is required to submit an extract task. See [pricing](/pages/documentation/platform/pricing) for billing details. ## Async lifecycle `POST /v1/extract` returns immediately with a task identifier and `status` set to `"pending"`. Subsequent calls to `GET /v1/extract/:id` return the current state until `status` is `"completed"` or `"failed"`. `GET /v1/extract` is also available to list all extract tasks for the account. See the [list reference](/pages/documentation/endpoints/extract/list). ```text theme={"system"} POST /extract GET /extract/:id GET /extract/:id │ │ │ ▼ ▼ ▼ { id, status: { id, status: "processing" } { id, status: "completed", "pending" } (poll) output: { resultUrl } } ``` Poll at 30 seconds for long-running tasks. Polling above 1 request per second will be rate-limited. ## Example Create a Linkup account for free to get your API key. ```shell curl theme={"system"} # 1. Submit curl -X POST "https://api.linkup.so/v1/extract" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "q": "All engineering team members with their name, role, and profile page", "url": "https://example.com/team", "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Full name" }, "role": { "type": "string" }, "profileUrl": { "type": "string", "format": "uri" } }, "required": ["name", "profileUrl"] }, "verifyUrls": true }' # → { "id": "01234-abcd-56789", "status": "pending", ... } # 2. Poll curl "https://api.linkup.so/v1/extract/01234-abcd-56789" \ -H "Authorization: Bearer $LINKUP_API_KEY" # 3. Download the rows once status is "completed" curl "" ``` `POST /v1/extract` returns the task envelope immediately, with `status` set to `"pending"` and `output` set to `null`. `GET /v1/extract/{id}` returns the same envelope; once `status` is `"completed"`, `output` is populated: ```json theme={"system"} { "id": "01234-abcd-56789", "type": "extract", "status": "completed", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:03:12.000Z", "error": null, "input": { "q": "All engineering team members with their name, role, and profile page", "url": "https://example.com/team", "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Full name" }, "role": { "type": "string" }, "profileUrl": { "type": "string", "format": "uri" } }, "required": ["name", "profileUrl"] }, "verifyUrls": true }, "output": { "creditsUsed": 2.84, "resultUrl": "https:///extract/01234-abcd-56789.ndjson?...", "rowsReturned": 24 } } ``` Each line of the downloaded NDJSON file is one row matching `schema` (or the shape inferred from `q` and the data structure on the website): ```text theme={"system"} {"name": "Ada Lovelace", "role": "Staff Engineer", "profileUrl": "https://example.com/team/ada"} {"name": "Alan Turing", "role": "Principal Engineer", "profileUrl": "https://example.com/team/alan"} ``` ## Next Query phrasing, schema design, and polling. Tool definition and integration prompt. Full parameter spec and response schema. # /extract Source: https://docs.linkup.so/pages/documentation/endpoints/extract/post https://api.linkup.so/v1/openapi.json POST /v1/extract [BETA] The `POST /extract` method creates an asynchronous extract task. Create a Linkup account for free to get your API key. This is a **closed-beta**, **asynchronous** endpoint — [request access](mailto:contact@linkup.so?subject=Request%20access%20to%20%2Fextract%20beta) before calling it. It immediately returns the extract task identifier, before the task is completed. Use [`GET /v1/extract/:id`](/pages/documentation/endpoints/extract/get) to poll for the result, or [`GET /v1/extract`](/pages/documentation/endpoints/extract/list) to list all your extract tasks. For an explanation of `q`, `url`, `schema`, and `verifyUrls`, see the [Extract overview](/pages/documentation/endpoints/extract/overview). # Fetch best practices Source: https://docs.linkup.so/pages/documentation/endpoints/fetch/best-practices Retrieval modes, JavaScript rendering, structured JSON from a known URL, pairing /fetch with /search, and operational constraints. This page covers when to use the `"pro"` mode, when to render JavaScript, when to extract raw HTML or image URLs, when to ask **Fetch** for typed JSON, and how to pair **Fetch** with [**Search**](/pages/documentation/endpoints/search/overview). ## Choosing a retrieval mode Use `"standard"` for `mode` on regular pages. It is the default and the most cost-effective option. Use `"pro"` for `mode` on hard-to-retrieve pages. Pro fetch delivers significantly higher success rates on hard-to-retrieve pages. It has a higher per-call cost. `mode` and `renderJs` are independent. Choose `mode` based on how difficult the page is to access, and choose `renderJs` based on whether the page needs JavaScript to display its content. ```json theme={"system"} { "url": "https://example.com/hard-to-retrieve-page", "mode": "pro", "renderJs": false } ``` ## When to render JavaScript Many modern sites load content via JavaScript. Within an agentic pipeline, setting `renderJs` to `true` is the safer default: it ensures the full content of the page is extracted and provided to the agent. Setting `renderJs` to `false` is appropriate when targeting a known set of static pages, once the specific site has been confirmed to return full content without JavaScript rendering. The default is `false` and the no-JS rate is cheaper (\$0.001 vs \$0.005 per call). Indicators that `renderJs` should be `true`: * The returned markdown is substantially shorter than the live page. * The output contains repeated boilerplate such as "Loading..." or "JavaScript is required". * Sections visible in a browser are missing entirely from the returned markdown. ## Pairing with Search A common pattern uses **Search** to find candidate URLs and **Fetch** to retrieve them in full when the agent needs the entire page rather than the snippets returned by **Search**. ```python theme={"system"} search_results = client.search( query="Datadog pricing tiers and per-host costs", depth="standard", output_type="searchResults", ) for result in search_results.results[:3]: page = client.fetch(url=result.url, render_js=True) # feed page.markdown into your LLM, or pass schema to get typed JSON in data ``` Selection of pages to fetch can be done: * agentic: ask the agent to fetch the most relevant pages based on the page snippets returned by **Search**. * programmatic: set `maxResults` in **Search** and fetch all URLs. ## Working with raw HTML and images **Fetch** returns clean markdown by default. Two flags add adjacent representations of the same page when the markdown alone is insufficient. ### `extractImages` Set `extractImages` to `true` to additionally return a list of image URLs found on the page (product photos, charts on a financial page, recipe images). Adds latency; enable only for workflows that consume image URLs. ### `includeRawHtml` Set `includeRawHtml` to `true` for: * workflows that need to operate on the full page HTML; * pages whose structure (complex tables, embedded widgets) is erased during markdown conversion. Both flags default to `false` and can be combined with `renderJs` set to `true`. They are independent of `schema`. ## Structured output Use `schema` when you already have a URL and want typed JSON from **this page**. Default **Fetch** stays markdown-only; `schema` is the switch that adds `data`. Put the shape and per-field meaning in the schema. Field `description`s tell the model what to look for. `schema` without `instructions` is valid. Use `instructions` for global rules the schema cannot express: * "public list prices only" * "express monetary values in USD" * "if a role is listed in several cities, emit one item per city" `instructions` only steers schema fill. It does not trim or rewrite `markdown`. `instructions` without `schema` returns a `400`. Maximum 4,000 characters. Keep the schema a JSON Schema object (`type` `"object"`). Keep it shallow: primitive fields and one level of arrays are more reliable than deep nesting. If a field is not on the page, it is omitted from `data` — including fields marked `required`. The model does not invent values. **Fetch** with `schema` reads this URL only. It does not follow links and it does not crawl a site. Adding a `schema` costs a flat \$0.001 on top of whatever the call would have cost as markdown. See the [Fetch pricing table](/pages/documentation/endpoints/fetch/overview#pricing) for every combination. | Job | Use | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | One known URL, markdown | **Fetch** | | One known URL, typed JSON from that page | **Fetch** with `schema` | | Many rows from a listing page, possibly following links | [**Extract**](/pages/documentation/endpoints/extract/overview) (closed beta) | | No URL yet, structured JSON from the web | [**Search**](/pages/documentation/endpoints/search/overview) with `outputType` `"structured"` and `structuredOutputSchema` | Search keeps `structuredOutputSchema`. **Fetch** uses `schema` and `instructions`. ```json theme={"system"} { "url": "https://www.linkup.so/careers", "schema": { "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position" }, "city": { "type": "string", "description": "The city where the job is located" } } } } } }, "instructions": "If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city." } ``` ## Common pitfalls Bad → Fix pairs grounded in the documented constraints of **Fetch** (HTML and PDF, 20 MB HTML / 100 MB PDF caps, anonymous, optional JavaScript rendering for HTML pages). **SPA fetched without `renderJs`.** The markdown comes back near-empty because the content is rendered client-side. ```json Bad theme={"system"} { "url": "https://app.example.com/dashboard", "renderJs": false } ``` ```json Fix theme={"system"} { "url": "https://app.example.com/dashboard", "renderJs": true } ``` **Passing an unsupported binary URL.** **Fetch** supports HTML and PDF. ZIPs, images, videos, and other binary content return a `400` error. ```json Bad theme={"system"} { "url": "https://example.com/archive.zip" } ``` ```json Fix theme={"system"} { "url": "https://example.com/whitepaper.pdf" } ``` **Expecting Fetch to retrieve content behind a login wall.** The endpoint is anonymous and returns what a logged-out visitor would see. ```json Bad theme={"system"} { "url": "https://app.example.com/private/report" } ``` ```json Fix theme={"system"} { "url": "https://example.com/public/press-release" } ``` **Sending `instructions` without `schema`.** Structured output has no prose mode. `instructions` only steers schema fill, so the request is rejected. ```json Bad theme={"system"} { "url": "https://example.com/pricing", "instructions": "Return list prices in USD" } ``` ```json Fix theme={"system"} { "url": "https://example.com/pricing", "schema": { "type": "object", "properties": { "plans": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Plan name" }, "priceUsd": { "type": "number", "description": "Public list price in USD" } } } } } }, "instructions": "Use public list prices only and express monetary values in USD." } ``` **Expecting Fetch to follow links or fill fields that are not on the page.** **Fetch** reads this URL only. Missing fields are omitted from `data`, not invented. For many rows across a listing, use [**Extract**](/pages/documentation/endpoints/extract/overview). ## Resources * [Fetch overview](/pages/documentation/endpoints/fetch/overview) * [Extract overview](/pages/documentation/endpoints/extract/overview) * [Search best practices](/pages/documentation/endpoints/search/best-practices) * [Errors](/pages/documentation/platform/errors) * [Rate limits](/pages/documentation/platform/rate-limits) # Fetch for AI agents Source: https://docs.linkup.so/pages/documentation/endpoints/fetch/for-agents Self-contained /fetch integration prompt for coding agents and tool-using LLMs that need full page content or typed JSON from a known URL. This page is structured for direct use as integration context for a coding agent, or as a function-calling tool definition. Operational guidance is repeated inline so the page is self-contained. *** # Linkup `/fetch` integration guide You are integrating the **Linkup `/fetch` API**: a real-time page content extractor. Given a URL, it returns clean LLM-ready markdown. Optional JavaScript rendering is available for client-side-rendered pages, along with optional raw HTML and image URL extraction. `"pro"` delivers significantly higher success rates on hard-to-retrieve pages. Pass `schema` to also get typed JSON from that same page in `data`. Limits: HTML up to 20 MB, PDF up to 100 MB. HTML and PDF only. Markdown-only latency is typically \~1s. Structured output adds a model pass after the scrape and is slower. ## When to use it Use `/fetch` when the URL is already known: typically after `/search` has narrowed the candidates, or when a user has supplied the URL directly. Use `schema` on `/fetch` when you already have the URL and want typed JSON from **this page**. Do not use `/extract` for a single page's JSON object. Do not use Search `structuredOutputSchema` when the URL is already known. Other endpoints in the API: * [**Search**](/pages/documentation/endpoints/search/overview) (`/search`): when no URL is available and web content must be located. Use `outputType` `"structured"` and `structuredOutputSchema` only when there is no URL yet. * [**Research**](/pages/documentation/endpoints/research/overview) (`/research`): autonomous research agent. Async, 2–20 minutes depending on depth. * [**Extract**](/pages/documentation/endpoints/extract/overview) (`/extract`): many structured rows from a listing page, possibly following links. Closed beta, async. * [**Tasks**](/pages/documentation/endpoints/tasks/overview) (`/tasks`): asynchronous batch wrapper around **Search**, **Fetch**, and **Research**. Fetch tasks accept the same `schema` and `instructions` as `POST /fetch`. ## Setup ```bash theme={"system"} pip install linkup-sdk # Python # or npm install linkup-sdk # TypeScript ``` ```bash theme={"system"} export LINKUP_API_KEY="your-api-key" ``` ## Example (Python; adapt to the project's language) ```python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") # renderJs: true is the safer default for unknown sites. # Many modern marketing sites and SPAs require it. response = client.fetch( url="https://docs.linkup.so", mode="standard", render_js=True, ) print(response) ``` Structured JSON from a known URL (if the SDK release does not accept `schema` / `instructions`, POST the same JSON to `https://api.linkup.so/v1/fetch`): ```python theme={"system"} schema = { "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position", }, "city": { "type": "string", "description": "The city where the job is located", }, }, }, } }, } response = client.fetch( url="https://www.linkup.so/careers", schema=schema, instructions="If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city.", ) print(response.data) ``` ## Tool definition (OpenAI function-calling format) Remove the `"type": "function"` envelope and rename `parameters` to `input_schema` for the Anthropic format. ```json theme={"system"} { "type": "function", "function": { "name": "linkup_fetch", "description": "Fetches a URL and returns clean LLM-ready markdown. Optionally extracts typed JSON into data when schema is provided. Supports HTML pages up to 20 MB and PDF pages up to 100 MB. Handles JavaScript-rendered HTML pages when renderJs is true. Use mode=pro for hard-to-retrieve pages. Use whenever the URL is already known. Use schema when you need typed JSON from this page; do not follow links.", "parameters": { "type": "object", "properties": { "url": { "type": "string", "description": "The URL to fetch. Must be a valid HTTP/HTTPS URL pointing to an HTML or PDF page." }, "mode": { "type": "string", "enum": ["standard", "pro"], "description": "Retrieval mode. Use standard for regular pages. pro delivers significantly higher success rates on hard-to-retrieve pages.", "default": "standard" }, "renderJs": { "type": "boolean", "description": "Render the page's JavaScript before extraction. Default false. For agentic pipelines, set to true by default to ensure the full page content is extracted; many modern sites require it. Set to false only after the specific site has been confirmed to render server-side.", "default": false }, "includeRawHtml": { "type": "boolean", "description": "Return the raw HTML alongside the markdown. Default false. Set to true for workflows that need full page HTML, or when complex structure (tables, custom elements) is erased during markdown conversion.", "default": false }, "extractImages": { "type": "boolean", "description": "Extract image URLs alongside the markdown. Default false. Set to true for workflows that consume images (product photos, charts, recipe images).", "default": false }, "schema": { "type": "object", "description": "Optional JSON Schema of type object describing the structured data to extract from this page. When set, the response includes data. Field descriptions tell the model what to look for. Fields with no grounded value are omitted, even if marked required. This page only — does not follow links." }, "instructions": { "type": "string", "description": "Optional extra rules the schema cannot express (currency, which prices to keep, how to split rows). Requires schema. Maximum 4000 characters. Does not trim markdown. Invalid without schema." } }, "required": ["url"] } } } ``` ## Operational guidance (inline) ### `mode` selection Use `"standard"` for `mode` by default and `"pro"` for hard-to-retrieve pages. `"pro"` delivers significantly higher success rates on hard-to-retrieve pages. It costs more. `mode` and `renderJs` solve different problems: `mode` controls how the page is accessed, while `renderJs` controls whether JavaScript is executed. ### `renderJs` selection For agentic pipelines, set `renderJs` to `true` by default to ensure the full content of each page is extracted. A page that requires JavaScript rendering and does not receive it returns near-empty markdown. Set `renderJs` to `false` only when all the URLs to be accessed have been confirmed to render server-side. Cost depends on `mode` and `renderJs`. Adding a `schema` for [structured output](/pages/documentation/endpoints/fetch/overview#structured-output) adds a flat \$0.001 on top of any combination. | `mode` | `renderJs` | Markdown only | With `schema` | | ------------ | ---------- | ---------------- | ---------------- | | `"standard"` | `false` | \$0.001 per call | \$0.002 per call | | `"standard"` | `true` | \$0.005 per call | \$0.006 per call | | `"pro"` | `false` | \$0.005 per call | \$0.006 per call | | `"pro"` | `true` | \$0.01 per call | \$0.011 per call | Failed calls are not billed. ### When to use Fetch **Fetch** is purpose-built to extract the content of a webpage that has been identified. Linkup's **Fetch** can be used as a stronger alternative to typical fetch tools because it can render JavaScript. Add `schema` when the caller needs typed JSON from that page rather than parsing markdown itself. ### The `schema` parameter * Required to turn structured output on. `instructions` is optional. * Must be a JSON Schema object (`type` `"object"`). Arrays as the root type are rejected. * Field `description`s do the extraction work. Keep the schema shallow. * `data` is omitted when `schema` is omitted. `markdown` is always returned. * Missing fields are omitted from `data`, not set to `null` and not invented. * `instructions` without `schema` returns a `400`. * Do not use Search's `structuredOutputSchema` on **Fetch**. ### The Search → Fetch pattern ```python theme={"system"} search = client.search( query="Datadog pricing tiers", depth="standard", output_type="searchResults", ) for r in search.results[:3]: page = client.fetch(url=r.url, render_js=True) # feed page.markdown to your LLM, or pass schema to get page.data ``` Use **Search** to find candidate URLs and **Fetch** to retrieve them in full when the agent needs to reason over the full page content rather than the snippets returned by **Search**. When the next step is typed fields from a chosen URL, call **Fetch** with `schema` instead of Search `structuredOutputSchema`. ### Constraints * **Fetch** supports HTML and PDF. Other binary URLs (ZIPs, images, videos) return a `400` error. * **Fetch** returns a `400` error for HTML pages over 20 MB and PDFs over 100 MB. * **Fetch** does not authenticate. It returns the response a logged-out visitor would see. * Setting `extractImages` to `true` adds latency; enable it for workflows that consume image URLs. * `includeRawHtml` should be `true` only when the raw HTML is required. * **Fetch** does not follow links. For many rows from a listing page, use `/extract`. * If structured extraction fails after a successful scrape, the call returns a `400` and is not billed. ## TypeScript notes * Import: `import { LinkupClient } from 'linkup-sdk'`. * Method: `await client.fetch({ url, mode, renderJs, schema, instructions })`. Single object argument. * Field names are camelCase: `mode`, `renderJs`, `includeRawHtml`, `extractImages`, `schema`, `instructions`. * If the installed SDK types omit `schema` / `instructions`, POST the JSON body to `/v1/fetch` directly. ## Links * [Fetch overview](/pages/documentation/endpoints/fetch/overview) * [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices) * [API reference](/pages/documentation/endpoints/fetch/reference) * [OpenAPI spec](https://api.linkup.so/v1/openapi.json) # Fetch overview Source: https://docs.linkup.so/pages/documentation/endpoints/fetch/overview Real-time page content extractor. Clean, LLM-ready markdown from any public URL — HTML or PDF — with optional typed JSON from the same page. **Fetch** is Linkup's real-time page content extractor. Given a URL, the `/fetch` route returns a clean markdown rendering of the page, optimized for LLM consumption. HTML and PDF documents are supported. Optional JavaScript rendering is available for HTML pages, along with optional raw HTML and extracted image URLs. `"pro"` delivers significantly higher success rates on hard-to-retrieve pages. Pass a JSON Schema in `schema` to also get typed JSON from that same page in `data`. Default **Fetch** is unchanged: URL in, markdown out. Structured output is an optional extra on that same request. ## Parameters | Parameter | Default | Description | | ---------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `url` | *(required)* | The page to fetch. Must be a valid HTTP/HTTPS URL. | | `mode` | `"standard"` | Retrieval mode. Use `"standard"` for regular pages. `"pro"` delivers significantly higher success rates on hard-to-retrieve pages. | | `renderJs` | `false` | Render the page's JavaScript before extraction. Required for client-side-rendered pages (SPAs, dashboards, many modern marketing sites). See [pricing](#pricing) for the rendered-call rate. | | `includeRawHtml` | `false` | Return the original HTML alongside the markdown. Used for custom parsing or preservation of structures that markdown does not represent. | | `extractImages` | `false` | Return a separate list of image URLs alongside the markdown. | | `schema` | *(optional)* | JSON Schema of `type` `"object"` describing the JSON to extract from the page. When present, the response includes `data`. Field `description`s tell the model what to look for. | | `instructions` | *(optional)* | Extra rules the schema cannot express, such as currency or which prices to keep. Requires `schema`. Maximum 4,000 characters. | In agentic pipelines, set `renderJs` to `true` by default. Many modern sites are client-side rendered; switch to `false` only after confirming the specific site renders server-side. When and how to use `mode`, `renderJs`, `includeRawHtml`, `extractImages`, `schema`, and `instructions` is covered on the [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices) page. ## Structured output `schema` turns structured output on. `instructions` is optional: `schema` without `instructions` is valid, and field descriptions do the work. When `schema` is present: * Existing response fields stay. Markdown is still returned. `data` is an object matching the schema for fields found on the page. * Fields with no grounded value are omitted, even if the schema marks them `required`. Values are not invented. * `instructions` without `schema` returns a `400`. * `schema` must be a JSON Schema object (`type` `"object"`). An array or other root type returns a `400`. * `mode`, `renderJs`, `includeRawHtml`, and `extractImages` are independent of structured output. * **Fetch** reads this URL only. It does not follow links. For many rows across a listing page, use [**Extract**](/pages/documentation/endpoints/extract/overview). * [**Search**](/pages/documentation/endpoints/search/overview) structured output stays on `structuredOutputSchema`. **Fetch** uses `schema` and `instructions`. Structured output adds a model pass after the scrape, so the call is slower than markdown-only **Fetch** and costs \$0.001 more. See [pricing](#pricing). ## Constraints **Fetch** supports HTML and PDF. Other binary URLs (ZIP, image, video) return a `400` error. **Fetch** does not authenticate. Pages behind a login wall return what an anonymous visitor would see. HTML pages over 20 MB and PDFs over 100 MB return a `400` error. See the [rate-limits page](/pages/documentation/platform/rate-limits) for request-rate limits. ## Pricing Cost depends on `mode` and `renderJs`. Adding a `schema` for [structured output](/pages/documentation/endpoints/fetch/overview#structured-output) adds a flat \$0.001 on top of any combination. | `mode` | `renderJs` | Markdown only | With `schema` | | ------------ | ---------- | ---------------- | ---------------- | | `"standard"` | `false` | \$0.001 per call | \$0.002 per call | | `"standard"` | `true` | \$0.005 per call | \$0.006 per call | | `"pro"` | `false` | \$0.005 per call | \$0.006 per call | | `"pro"` | `true` | \$0.01 per call | \$0.011 per call | ## Example Create a Linkup account for free to get your API key. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") response = client.fetch( url="https://docs.linkup.so", mode="standard", render_js=True, ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const response = await client.fetch({ url: 'https://docs.linkup.so', mode: 'standard', renderJs: true, }); console.log(response); ``` ```shell curl theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://docs.linkup.so", "mode": "standard", "renderJs": true }' ``` Successful response: ```json theme={"system"} { "markdown": "# Linkup docs\n\nGet started for free, no credit card required...", "rawHtml": "...", "images": [ { "alt": "Linkup logo", "url": "https://docs.linkup.so/images/logo-light.svg" } ] } ``` `rawHtml` is present only when `includeRawHtml` is `true`. `images` is present only when `extractImages` is `true`. `markdown` is always returned. ### Structured output example Structured output is live on `POST /v1/fetch`. If your current SDK release does not accept `schema` or `instructions`, send the same JSON body as the curl example. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") schema = { "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position", }, "city": { "type": "string", "description": "The city where the job is located", }, }, }, } }, } response = client.fetch( url="https://www.linkup.so/careers", schema=schema, instructions="If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city.", ) print(response.data) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const schema = { type: 'object', properties: { jobs: { type: 'array', items: { type: 'object', properties: { position: { type: 'string', description: 'The job title or position', }, city: { type: 'string', description: 'The city where the job is located', }, }, }, }, }, }; const response = await client.fetch({ url: 'https://www.linkup.so/careers', schema, instructions: 'If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city.', }); console.log(response.data); ``` ```shell curl theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.linkup.so/careers", "schema": { "type": "object", "properties": { "jobs": { "type": "array", "items": { "type": "object", "properties": { "position": { "type": "string", "description": "The job title or position" }, "city": { "type": "string", "description": "The city where the job is located" } } } } } }, "instructions": "If a position is listed in multiple cities, emit one jobs item per city so jobs[].city is a single city." }' ``` Successful response (abbreviated). `markdown` is still returned; `data` is present because `schema` was set: ```json theme={"system"} { "markdown": "# Careers\n\n...", "data": { "jobs": [ { "position": "Talent Acquisition", "city": "New York" }, { "position": "Founding DevRel Engineer", "city": "NYC (NY)" }, { "position": "Founding DevRel Engineer", "city": "San Francisco (CA)" } ] } } ``` ## Next JavaScript rendering, structured output, pairing with Search. Tool definition and integration prompt. Full parameter spec and response schema. # /fetch Source: https://docs.linkup.so/pages/documentation/endpoints/fetch/reference https://api.linkup.so/v1/openapi.json POST /v1/fetch The `/fetch` endpoint allows you to fetch a single webpage from a given URL. Create a Linkup account for free to get your API key. For when to set `mode`, `renderJs`, `includeRawHtml`, `extractImages`, `schema`, and `instructions`, see the [Fetch overview](/pages/documentation/endpoints/fetch/overview). # Research best practices Source: https://docs.linkup.so/pages/documentation/endpoints/research/best-practices Mode and reasoning-depth selection, question phrasing, polling, and failure handling for /research. This page covers how to pick a mode and reasoning depth, how to phrase a research prompt, and how to poll and handle failures on the **Research** endpoint. ## Choosing a mode `"answer"` typically iterates to verify the response. The agent reasons against itself, checks alternative response candidates, and cross-references the evidence to produce a definitive answer with a high level of certainty. Use it when verified answers are required, typically for high-stakes workflows where getting the answer right is crucial (finance, legal, research, etc.). `"investigate"` is optimized to go deep on a single topic or entity. The agent follows threads uncovered during the search, explores new trails as they are discovered, and verifies claims along the way. Use it to build deep-dive reports on single entities or for complex, multi-hop questions. `"research"` is optimized to go wide. The agent searches multiple threads in parallel to produce structured reports that cover one topic broadly, or many topics or entities at once. Use it to build industry reports or lists of entities. ### Example prompts ```text theme={"system"} What is the American company that generated the highest revenues in Europe in 2025? ``` ```text theme={"system"} Build a detailed report on the company Anthropic from the perspective of a lawyer. ``` ```text theme={"system"} List all the AI startups that raised more than a $10M seed round in Europe in the last 6 months. For each, generate a short description and include its website. ``` If `mode` is not provided, the agent classifies the question and selects one of the three modes for the request. Setting `mode` explicitly is the recommended path because it produces the most predictable latency, cost, and output shape. The canonical mode table lives on the [Research overview](/pages/documentation/endpoints/research/overview#modes). ## Choosing a reasoning depth `reasoningDepth` controls how much effort the agent puts into the research. The search, retrieval, and iteration budget grows with the depth, and the agent is aware of its compute budget: it typically reasons until it is satisfied with the answer, within the limit of that budget. As such, a task run at `"XL"` does not necessarily produce a much longer answer if a satisfactory response does not require it — but the agent is more demanding and searches more. Adapt the depth to budget, latency requirements, and the complexity of the request. The canonical depth table (latency ranges per `"S"` / `"M"` / `"L"` / `"XL"`) lives on the [Research overview](/pages/documentation/endpoints/research/overview#reasoning-depth). ## Question phrasing **Research** runs as an agentic loop: the agent interprets the question, plans its retrieval, executes searches in parallel, verifies claims, and synthesizes the result. Both terse and detailed inputs are accepted, and more precise input produces more predictable, more thorough, and more aligned output. Useful dimensions to specify include: * the angles to cover, * the leads to pursue, * the facts to verify, * the entities to compare, * the constraints any answer must satisfy, and * the structure expected from the final response. ### Examples A short prompt; the agent chooses the angles and the level of detail. ```text theme={"system"} What's going on in the European AI inference market? ``` A medium prompt; angle and deliverable are constrained, individual claims are not. ```text theme={"system"} Survey the European AI inference market in 2026: identify the main hyperscalers and independent inference providers operating in the region, their pricing posture, and the regulatory pressure they face under the EU AI Act. ``` A fully specified agent brief; entities, comparison axes, and verification expectations are all named. ```text theme={"system"} Produce a competitive landscape of European AI inference providers operating in 2026. Scope: - Cover at minimum: Mistral, Aleph Alpha, Silo AI, OVHcloud, Scaleway, and any independent inference provider with publicly disclosed funding above €20M. - Exclude US- and APAC-headquartered hyperscalers unless they operate a sovereign EU inference offering. For each provider, surface: - Headquarters and primary inference regions. - Models served and deployment modes (managed API, dedicated, on-prem). - Disclosed pricing for one mid-sized open-weight model, normalized per million input tokens. - Sovereignty and EU AI Act posture: data residency claims, certifications, and any public stance on GPAI obligations. - Latest disclosed funding round and lead investors. Verify all numeric claims against primary sources (provider pricing pages, regulatory filings, or press releases). Flag any figure that could only be sourced from secondary aggregators. ``` ## Polling Polling loops are a common source of integration errors. Recommended defaults: * **Initial interval**: 2 seconds. * **Backoff**: double the interval up to 10 seconds for longer tasks. * **Maximum poll rate**: 1 request per second. Higher rates trigger rate limits without reducing time-to-completion. For long-running research, submit via the [**Tasks**](/pages/documentation/endpoints/tasks/overview) endpoint and check periodically rather than maintaining a blocking polling loop. ## Failure handling No credit is deducted for failed tasks or tasks that return no result. Retries are unrestricted. This policy is consistent with the other endpoints. See [errors](/pages/documentation/platform/errors). ## Resources * [Research overview](/pages/documentation/endpoints/research/overview) * [Structured output tutorial](/pages/documentation/tutorials/structured-output-guide) * [Filtering tutorial](/pages/documentation/tutorials/filtering) * [Tasks best practices](/pages/documentation/endpoints/tasks/best-practices) (for long-running batches) * [Errors](/pages/documentation/platform/errors) and [pricing](/pages/documentation/platform/pricing) # Research for AI agents Source: https://docs.linkup.so/pages/documentation/endpoints/research/for-agents Self-contained /research integration prompt for coding agents and tool-using LLMs that need multi-step web research. This page is structured for direct use as integration context for a coding agent, or as a function-calling tool definition. Operational guidance is repeated inline so the page is self-contained. *** # Linkup `/research` integration guide You are integrating the **Linkup `/research` API**: an autonomous agent that investigates the web to handle questions a single search query cannot resolve. Use cases include verified answers to precise questions, focused investigations of a defined subject, and broad multi-angle reports. The agent gathers evidence from multiple sources in parallel, iterates through investigation, and returns a sourced response with inline citations. Three modes (`"answer"`, `"investigate"`, `"research"`; explicit selection produces the most predictable behaviour), four reasoning depths (`"S"`, `"M"`, `"L"`, `"XL"`), two output types (`"sourcedAnswer"`, `"structured"`). Latency: 2–20 minutes depending on depth. Async lifecycle. ## When to use it Use `/research` for multi-source synthesis, comparative analysis, or audit-trail citations: questions where the value is the agent's planning and synthesis across many sources. Other endpoints in the API: * [**Search**](/pages/documentation/endpoints/search/overview) (`/search`): synchronous web search, \<1s–\~30s depending on depth. Three modes, three output types. * [**Fetch**](/pages/documentation/endpoints/fetch/overview) (`/fetch`): when the URL is already known. * [**Tasks**](/pages/documentation/endpoints/tasks/overview) (`/tasks`): asynchronous batch wrapper. ## Setup ```bash theme={"system"} pip install linkup-sdk # Python # or npm install linkup-sdk # TypeScript ``` ```bash theme={"system"} export LINKUP_API_KEY="your-api-key" ``` ## Example (Python; adapt to the project's language) ```python theme={"system"} import time from linkup import LinkupClient client = LinkupClient(api_key="") # /research is async: POST returns immediately with an id. Poll until completed. # # mode: type of investigation. Setting it explicitly produces the most # predictable latency, cost, and output shape. If omitted, the agent # classifies the question and selects one of the three modes for the request. # - Answer: precise, evidence-backed answer with a definitive solution. # - Investigate: focused report on a single defined subject. Narrower than Research. # - Research: structured report organized by theme, covering many topics in parallel. # # reasoningDepth (default L): trade latency for thoroughness. # - S: light coverage, 2-5 min. Short multi-step investigations. # - M: balanced cost-to-quality, 3-7 min. Routine use. # - L: thorough investigation, 5-10 min. High-quality answers under bounded latency. # - XL: exhaustive coverage, 10-20 min. Deliverables where completeness takes precedence over latency. task = client.research.create( q="Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", output_type="sourcedAnswer", mode="investigate", reasoning_depth="L", ) # Poll: 2-second initial interval, back off to 10s. interval = 2 while True: result = client.research.get(task.id) if result.status in ("completed", "failed"): break time.sleep(interval) interval = min(interval * 2, 10) print(result.output) ``` ## Tool definition (OpenAI function-calling format) Remove the `"type": "function"` envelope and rename `parameters` to `input_schema` for the Anthropic format. Note that this tool is **async**: the handler should poll on the model's behalf and return the completed result, not the task id. ```json theme={"system"} { "type": "function", "function": { "name": "linkup_research", "description": "Submits an autonomous research task and returns a synthesized, cited answer. Use for questions a single search query cannot resolve: verified answers to precise questions, focused investigations of a defined subject, or broad multi-angle reports. Latency is 2-20 minutes depending on reasoningDepth; the handler should poll until completion.", "parameters": { "type": "object", "properties": { "q": { "type": "string", "description": "The research question, phrased in natural language. Both terse and detailed inputs are accepted; more precise input produces more predictable, thorough, and aligned output. Useful dimensions to specify include the angles to cover, the leads to pursue, the facts to verify, the entities to compare, the constraints any answer must satisfy, and the structure expected from the final response. The agent plans and executes the retrieval steps." }, "outputType": { "type": "string", "enum": ["sourcedAnswer", "structured"], "description": "sourcedAnswer for natural-language answer with citations. structured for JSON matching structuredOutputSchema (keep the schema shallow)." }, "structuredOutputSchema": { "type": "string", "description": "Required when outputType=structured. JSON Schema (as a string) for the response. Root must be type 'object'. Keep flat. Deeply nested arrays of objects multiply latency and failure rate." }, "mode": { "type": "string", "enum": ["answer", "investigate", "research"], "description": "Type of investigation. Setting this field explicitly produces the most predictable latency, cost, and output shape, and is the recommended path. answer for precise, evidence-backed answers with a definitive solution. investigate for a focused report on a single defined subject. research for a structured report organized by theme covering many topics or entities in parallel. If this field is omitted, the agent classifies the question and selects one of the three modes for the request." }, "reasoningDepth": { "type": "string", "enum": ["S", "M", "L", "XL"], "description": "Controls thoroughness, trading latency for coverage. S: light coverage, 2-5 min, suitable for short multi-step investigations. M: balanced cost-to-quality, 3-7 min, suitable for routine use. L (default): thorough investigation, 5-10 min, suitable for high-quality answers under bounded latency. XL: exhaustive coverage, 10-20 min, suitable for deliverables where completeness takes precedence over latency. Match the depth to budget, latency requirements, and the complexity of the request.", "default": "L" }, "includeDomains": { "type": "array", "items": { "type": "string" }, "description": "Restrict the search to up to 100 trusted domains. Strongly recommended. Improves quality and reduces latency." }, "excludeDomains": { "type": "array", "items": { "type": "string" }, "description": "Domains to exclude." }, "fromDate": { "type": "string", "description": "ISO 8601 (YYYY-MM-DD). Restrict to results on or after this date." }, "toDate": { "type": "string", "description": "ISO 8601 (YYYY-MM-DD). Restrict to results on or before this date." } }, "required": ["q", "outputType"] } } } ``` ## Operational guidance (inline) ### Mode selection Setting `mode` explicitly produces the most predictable latency, cost, and output shape. Match the value to the workload: * `"answer"` for precise, evidence-backed answers with a definitive solution. * `"investigate"` for a focused report on a single defined subject. * `"research"` for a structured report organized by theme covering many topics or entities in parallel. If `mode` is not provided, the agent classifies the question and selects one of the three modes for the request; the selection depends on that classification. ### Reasoning-depth selection Match the depth to budget, latency requirements, and the complexity of the request: * Short multi-step investigations, latency-sensitive: `"S"` (2–5 min) * Routine use: `"M"` (3–7 min) * High-quality answers under bounded latency (default): `"L"` (5–10 min) * Deliverables where completeness takes precedence over latency: `"XL"` (10–20 min) ### Question phrasing **Research** runs as an agentic loop: the agent interprets the question, plans its retrieval, executes searches in parallel, verifies claims, and synthesizes the result. Both terse and detailed inputs are accepted; more precise input produces more predictable, more thorough, and more aligned output. Useful dimensions to specify include the angles to cover, the leads to pursue, the facts to verify, the entities to compare, the constraints any answer must satisfy, and the structure expected from the final response. ### Source filtering A handful of trusted domains in `includeDomains` improves quality and reduces latency. This setting is recommended whenever the use case has identifiable authoritative sources. ### `"structured"` schema design Flat schemas with primitive fields are fastest and most reliable. Reshape client-side when downstream code requires nesting. ### Polling * Initial interval: 2 seconds. * Backoff: maximum of 10 seconds. * Maximum poll rate: 1 request per second. ### Constraints * Date ranges should be set via `fromDate` / `toDate` rather than embedded in the question. * Deeply nested `structuredOutputSchema` values should be flattened and reshaped client-side. * Polling without backoff triggers rate limits without reducing time-to-completion. ## TypeScript notes * Import: `import { LinkupClient } from 'linkup-sdk'`. * Methods: `await client.research.create({ ... })`, `await client.research.get(id)`. * Field names are camelCase: `outputType`, `reasoningDepth`, `includeDomains`. ## Links * [Research overview](/pages/documentation/endpoints/research/overview) * [Research best practices](/pages/documentation/endpoints/research/best-practices) * [API reference: POST /research](/pages/documentation/endpoints/research/post) * [API reference: GET /research/:id](/pages/documentation/endpoints/research/get) * [Filtering tutorial](/pages/documentation/tutorials/filtering) * [Structured output tutorial](/pages/documentation/tutorials/structured-output-guide) * [OpenAPI spec](https://api.linkup.so/v1/openapi.json) # /research/:id Source: https://docs.linkup.so/pages/documentation/endpoints/research/get https://api.linkup.so/v1/openapi.json GET /v1/research/{id} The `GET /research/:id` method, with research task identifier, retrieves a specific research task information. Create a Linkup account for free to get your API key. This endpoint only retrieves data from an existing research task. It does not create a new research task. To create a new task, use [`POST /research`](/pages/documentation/endpoints/research/post). # /research Source: https://docs.linkup.so/pages/documentation/endpoints/research/list https://api.linkup.so/v1/openapi.json GET /v1/research The `GET /research` method, without research task identifier, lists all created research tasks for the authenticated organization, with pagination. Create a Linkup account for free to get your API key. This endpoint only retrieves data from existing research tasks. It does not create a new research task. To create a new task, use [`POST /research`](/pages/documentation/endpoints/research/post). # Research overview Source: https://docs.linkup.so/pages/documentation/endpoints/research/overview Research agent that navigates the web to produce sourced answers, from precise factual claims to multi-section reports. **Research** is Linkup's autonomous research agent that investigates the web to handle questions a single search query cannot resolve. Use cases include: * verified answers to precise questions, * focused investigations of a defined subject, and * broad multi-angle reports. The agent gathers evidence from multiple sources in parallel, iterates through investigation, and returns a sourced response with inline citations. **Research** is built around three modes, four reasoning depths, and two output types. Research results on the SealQA-0 benchmark Research ranks #1 on the SealQA-0 benchmark for sourced QA. ## Modes The `mode` parameter pins the type of investigation performed. | Mode | Description | Typical use | | --------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `"answer"` | Returns a precise, evidence-backed answer to a question with a definitive solution. | Hard questions with a single correct answer that require verification across multiple sources. *Example: "Which 12 S\&P 500 companies gained more than 50% with market capitalization above \$5B in Q3 2025?"* | | `"investigate"` | Returns a focused report on a single defined subject, examining each angle and verifying claims. | In-depth reads on a defined entity. *Example: "Risk profile and regulatory history of company X."* | | `"research"` | Returns a structured report organized by theme, covering many topics or entities in parallel. | Open-ended questions requiring breadth across multiple subjects. *Example: "State of the European generative AI market in 2026."* | Set `mode` explicitly to pin latency, cost, and output shape. Omitting it lets the agent classify the question and pick — convenient but less predictable. If the `mode` parameter is not provided, the agent automatically classifies the question and selects one of the three modes for the request. ## Reasoning depth The `reasoningDepth` parameter controls thoroughness. Higher depths have more compute budget: they consult more sources, perform more iterations and cross-checking, produce longer outputs, and take longer to run. | Depth | Description | Order-of-magnitude latency | Cost | | ------ | ------------------------------------------------------------------------------------------------ | -------------------------- | --------------- | | `"S"` | Light coverage. Suitable for short multi-step investigations. | 2–5 minutes | \$0.25 per call | | `"M"` | Balanced cost-to-quality ratio. Suitable for routine use. | 3–7 minutes | \$0.50 per call | | `"L"` | Thorough investigation. Suitable for high-quality answers under bounded latency. | 5–10 minutes | \$1.50 per call | | `"XL"` | Exhaustive coverage. Suitable for deliverables where completeness takes precedence over latency. | 10–20 minutes | \$2.50 per call | Omitting the parameter defaults to `"L"`. ## Output types | Value | Description | | ----------------- | -------------------------------------------------------------------------- | | `"sourcedAnswer"` | Natural-language answer with inline citations. | | `"structured"` | JSON object conforming to the schema provided in `structuredOutputSchema`. | For `"structured"`, see the [structured output tutorial](/pages/documentation/tutorials/structured-output-guide). ## Async lifecycle `POST /v1/research` returns immediately with a job identifier and `status` set to `"pending"`. Subsequent calls to `GET /v1/research/:id` return the current state until `status` is `"completed"` or `"failed"`. Typical completion times range from a couple of minutes for shallow configurations to twenty minutes for exhaustive ones. `GET /v1/research` is also available to list all research tasks for the account. ```text theme={"system"} POST /research GET /research/:id GET /research/:id │ │ │ ▼ ▼ ▼ { id, status: { id, status: "processing" } { id, status: "completed", "pending" } (poll) output: { ... } } ``` Poll at 5–10 seconds for long-running tasks. Polling above 1 request per second will be rate-limited. ## Example Create a Linkup account for free to get your API key. ```python python theme={"system"} from linkup import LinkupClient import time client = LinkupClient(api_key="") task = client.research.create( q="Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", output_type="sourcedAnswer", mode="investigate", reasoning_depth="L", ) while True: result = client.research.get(task.id) if result.status in ("completed", "failed"): break time.sleep(2) print(result.output) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const task = await client.research.create({ q: "Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", outputType: 'sourcedAnswer', mode: 'investigate', reasoningDepth: 'L', }); let result; do { await new Promise(r => setTimeout(r, 2000)); result = await client.research.get(task.id); } while (result.status !== 'completed' && result.status !== 'failed'); console.log(result.output); ``` ```shell curl theme={"system"} # 1. Submit curl -X POST "https://api.linkup.so/v1/research" \ -H "Authorization: Bearer $LINKUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "q": "Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", "outputType": "sourcedAnswer", "mode": "investigate", "reasoningDepth": "L" }' # → { "id": "task_..." } # 2. Poll curl "https://api.linkup.so/v1/research/task_..." \ -H "Authorization: Bearer $LINKUP_API_KEY" ``` `POST /v1/research` returns the task envelope immediately, with `status` set to `"pending"` and `output` set to `null`. `GET /v1/research/{id}` returns the same envelope; once `status` is `"completed"`, `output` is populated: ```json theme={"system"} { "id": "01234-abcd-56789", "type": "research", "status": "completed", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:08:42.000Z", "error": null, "input": { "q": "Compare the 2024 cloud revenue growth of Microsoft, Amazon, and Google.", "outputType": "sourcedAnswer", "mode": "investigate", "reasoningDepth": "L" }, "output": { "answer": "Microsoft Cloud revenue rose 23% to $137.4B in FY2024; AWS revenue rose 13% to $107.6B; Google Cloud rose 31% to $39.3B ...", "sources": [ { "name": "Microsoft 2024 Annual Report", "url": "https://www.microsoft.com/investor/reports/ar24/index.html", "snippet": "Microsoft Cloud revenue increased 23% to $137.4 billion." } // ... additional sources ] } } ``` When `outputType` is `"structured"`, `output` is the JSON object described by `structuredOutputSchema` instead of `{ answer, sources }`. ## Next Mode and depth selection, question structure, schema design. Tool definition and integration prompt. Full parameter spec and response schema. # /research Source: https://docs.linkup.so/pages/documentation/endpoints/research/post https://api.linkup.so/v1/openapi.json POST /v1/research The `POST /research` method creates an asynchronous research task which can perform a comprehensive web research. Create a Linkup account for free to get your API key. This is an **asynchronous** endpoint. It immediately returns the research task identifier, before it is completed. Use [`GET /v1/research/:id`](/pages/documentation/endpoints/research/get) to poll for the result, or [`GET /v1/research`](/pages/documentation/endpoints/research/list) to list all your research tasks. For an explanation of `mode`, `reasoningDepth`, and the async lifecycle, see the [Research overview](/pages/documentation/endpoints/research/overview). # Search best practices Source: https://docs.linkup.so/pages/documentation/endpoints/search/best-practices Depth selection, per-depth query patterns, source filtering, and schema design for /search. This page covers how to pick a depth, how to phrase queries for each of `"flash"`, `"fast"`, `"standard"`, and `"deep"`, how to choose an output type, and how to apply source and date filtering on the **Search** endpoint. ## Choosing depth The `depth` parameter controls if / how agentic search is used within the search. | Use case | Recommended setting | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Lowest latency | `"flash"` | | Keyword-style lookup at \~1s, higher-quality one-shot | `"fast"` | | Instruction-based retrieval comparable to one or a few Google searches | `"standard"` | | Breadth across adjacent keywords (news, research) | `"standard"` with explicit "run several searches with adjacent keywords" | | Scrape one URL provided in the query and run a search | `"standard"` | | Scrape several known URLs and run several searches | `"deep"` | | Find a URL, then scrape it | `"deep"` | Rule of thumb: lowest latency → `"flash"`; keyword lookup at \~1s → `"fast"`; if one or a few parallel Google searches would answer the question → `"standard"`; if a human would open multiple tabs or follow leads → `"deep"`. `"standard"` and `"deep"` can scrape URLs provided in the query. `"standard"` accepts one URL; `"deep"` accepts several and scrapes them with JavaScript rendering. ## Per-depth guidance `"flash"` is the lowest-latency mode. It is keyword-only and bypasses the LLM entirely. It returns results in under 200 ms. ### Behaviour * Single-pass retrieval against our index. No LLM involvement. * No query interpretation, no reformulation, no evaluation. * The query string is passed to the index as-is. * No in-query URL scraping, no sub-searches, no iteration. ### When to use it * Conversational AI use cases where latency is critical. * High-volume pipelines. * Lookups for one specific piece of information. ### When not to use it * Anything that requires reading a page (use `"standard"` for one URL, `"deep"` for several). * Any query whose intent depends on instruction parsing, ordering, or multi-step retrieval — `"flash"` will treat the instructions as keywords. ### Query shape Keep queries short and keyword-shaped: ```text theme={"system"} NVIDIA Q4 2024 revenue ``` ```text theme={"system"} Latest EU AI Act guidance ``` `"fast"` is keyword-only and bypasses the LLM entirely. It returns higher-quality one-shot results in about a second. For the tightest latency budget, use `"flash"`. ### Behaviour * Single-pass, keyword-like search with no LLM involvement. * No query interpretation, no reformulation, no evaluation. * The query string is passed to the index as-is. * No in-query URL scraping, no sub-searches, no iteration. ### When to use it * Conversational AI use cases where \~1s is acceptable and ranking quality matters. * High-volume pipelines that do not need agentic planning. * Lookups for one specific piece of information. ### When not to use it * Anything that requires reading a page (use `"standard"` for one URL, `"deep"` for several). * Any query whose intent depends on instruction parsing, ordering, or multi-step retrieval — `"fast"` will treat the instructions as keywords. * A latency budget under 200 ms — use `"flash"` instead. ### Query shape Keep queries short and keyword-shaped: ```text theme={"system"} NVIDIA Q4 2024 revenue ``` ```text theme={"system"} Current EUR/USD exchange rate ``` `"standard"` runs a single iteration of agentic search. The agent interprets the query, can split it into parallel sub-searches when appropriate, and can scrape one URL provided in the query string. Outputs from one iteration are not reused in another (an extracted URL cannot be passed back into a follow-up step within the same call). Latency is approximately 1–3 seconds. ### When to use it * Queries answerable by one well-targeted search. * Breadth across adjacent keywords for research and news, with an explicit instruction such as "run several searches with adjacent keywords". * A single known URL that needs to be combined with a search. * High-volume or low-latency use cases where the answer is likely found in one pass. ### Adjacent searches for breadth `"standard"` can run multiple searches in parallel within a single call. The agent fans out automatically when a query implies it, but stating the dimensions explicitly gives the caller direct control over coverage. | Use case | Prompt | | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | Company research | Build a profile on `{company}`. Run several searches to map (i) its products, (ii) its team, and (iii) its business model. | | News | Find recent news about OpenAI. Run several searches with adjacent keywords. | | Trends | What are people saying about AI agents on Twitter and Reddit? Run several searches. | For coverage-style workloads (news, trends), bare adjacent-search prompts are enough; the agent picks the angles. For workloads where specific dimensions matter (company research, competitive analysis), naming the dimensions in the prompt produces more reliable coverage. ### In-query URL scraping (one URL per call) `"standard"` can scrape one URL provided in the query. A combined search-and-scrape call: ```text theme={"system"} Scrape the website linkup.so. Also run a search to find articles, news, and posts mentioning linkup.so clients. Based on the content from the website and from the search, return a list of clients that use Linkup, with the source for each. ``` For workloads with several known URLs and adjacent searches, several `"standard"` calls or one `"deep"` call works best. `"deep"` runs several iterations of agentic search. It supports sequential instructions, where outputs from one step are used in the next (for example, search first, then scrape a discovered URL). Latency is approximately 5–30 seconds. ### When to use it * Sequential work where uterior steps depends on the first (locate a URL, then read it; find a list of pages, then scrape each). * Queries that explicitly require multiple searches and multiple scrapes. * Cases where information is not reliably found in a single pass. * Workloads where the requirements are undetermined and reliability matters more than latency. ### Sequential ordering Use instruction-style prompts. `"deep"` follows ordering literally, so name the steps: ```text theme={"system"} First find Datadog's pricing page URL. Then scrape that URL. Then return plan names, per-host prices, and included features as JSON. ``` For chained URL discovery (a common LinkedIn-style two-step pattern): ```text theme={"system"} First find LinkedIn posts on context engineering. Then, for each URL, extract the LinkedIn comments. ``` Compound prompts that decompose the work and assign a role to the agent also work well in `"deep"`: ```text theme={"system"} Your role is to map a company's value proposition from its website. Inputs: {company_name}, {company_website}. - First, find and scrape the homepage and primary product pages. - From each page, extract: headline claims, customer benefits, differentiator language, and CTAs. - Then, synthesize the extracted data into a summary of the value proposition. - Avoid vague marketing fluff. Focus on concrete external value claims. ``` ### In-query URL scraping (chained scrapes) `"deep"` can scrape multiple URLs in a single call, including URLs discovered during earlier iterations. This is essential for: * Detailed answers that rely on full pages rather than search snippets. * Returning a list of precise items found on a page (product characteristics, PDF links, downstream URLs). * Tasks requiring prices, images, or specifications that only appear on the source page. *** ## Chain with Fetch An alternative to in-query URL scraping: use **Search** to find candidate URLs, then call [**Fetch**](/pages/documentation/endpoints/fetch/overview) on the most relevant ones. This gives the caller direct control over which pages get scraped and how their content is processed downstream. ```python python theme={"system"} search = client.search(query="Datadog pricing tiers", depth="standard", output_type="searchResults") for r in search.results[:3]: page = client.fetch(url=r.url, render_js=True) # process page yourself ``` ```js js theme={"system"} const search = await client.search({ query: 'Datadog pricing tiers', depth: 'standard', outputType: 'searchResults', }); for (const r of search.results.slice(0, 3)) { const page = await client.fetch({ url: r.url, renderJs: true }); // process page yourself } ``` *** ## Prompting best practice `"standard"` and `"deep"` mode use [agentic search](/pages/documentation/endpoints/search/overview#agentic-search-standard-deep) and can follow instruction-style queries. `"flash"` and `"fast"` ignore natural language instructions. Queries should be split between: 1. **What the search must retrieve**: agentic search will optimize searches to find those elements. 2. **How the results should be reasoned over**: for `"sourcedAnswer"` and `"searchResults"`, how to use the data to answer a question. | Original phrasing | Recommended phrasing | Why | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | "How to estimate the annual IT costs of Total SA?" | "Find data sources that quantify Total SA's IT spend (annual reports, tech-vendor case studies, IT services contracts mentioning Total SA). For each, extract the figure and the year." | The first phrasing requests an answer; the second specifies retrievable evidence the agent can locate. | | "Tell me about the company linkup.so" | "Find the homepage, product pages, and about page for linkup.so. Extract: what the company does, target customers, pricing model, and known investors." | The first phrasing is unscoped; the second names targets and extraction fields. | A retrieval query has four components. The same shape applies across `"standard"` and `"deep"`, with longer instructions and explicit ordering on `"deep"`. From which perspective should the agent think. *Example*: "You are an expert GTM consultant" Where the agent should look. *Example*: "On the company domain `{company_domain}`, analyze homepage, about, and blog" What to extract. *Example*: "Include products, business model, target market, value proposition" Shape of the answer (`"sourcedAnswer"` or `"structured"`). *Example*: "Concise, business-oriented prose" ## Output type selection | Downstream consumer | Recommended setting | | ---------------------------------------- | ------------------------------------------------------------------------ | | LLM that will reason over the sources | `"searchResults"` | | End user, displayed directly | `"sourcedAnswer"` | | Code that parses fields, no URL yet | `"structured"` (with `structuredOutputSchema`) | | Code that parses fields from a known URL | [**Fetch**](/pages/documentation/endpoints/fetch/overview) with `schema` | For `"structured"`, see the [structured output tutorial](/pages/documentation/tutorials/structured-output-guide) for JSON-schema mechanics. ## Source filtering The full filtering parameter list lives on the [Search overview](/pages/documentation/endpoints/search/overview#filtering-and-date-controls). * Use `includeDomains` (up to 100) and `excludeDomains` (unlimited) for control over sources. See the [filtering tutorial](/pages/documentation/tutorials/filtering) for more. * Use `fromDate` and `toDate` (ISO 8601, `YYYY-MM-DD`) to restrict the index window. Note that some webpages (product pages, news) might have metadata publish date different from their latest update date, which makes filtering unstable. ## LinkedIn data extraction LinkedIn extraction is only available on the **Search** endpoint. The [**Fetch**](/pages/documentation/endpoints/fetch/overview) endpoint does not retrieve LinkedIn content. The **Search** endpoint can extract structured data from LinkedIn profile and company pages, and can surface posts by keyword. | Target | Query formulation | | -------------------------- | ------------------------------------------------ | | Person or company profile | `{linkedin_url}` + `Return the profile details.` | | Person or company posts | `{linkedin_url}` + `Return the recent posts.` | | Person or company comments | `{linkedin_url}` + `Return the comments.` | | Topic search | `Search for LinkedIn posts on {keyword}.` | `{linkedin_url}` is a person URL (`linkedin.com/in/{slug}`) or a company URL (`linkedin.com/company/{slug}`). LinkedIn data extraction only works with the **exact** LinkedIn profile or company URL. Shortened links, search-result fragments, or partial slugs will not return reliable data. When the URL isn't known up front, use `"deep"` to find the profile and scrape it in the same call. ```text theme={"system"} First find the LinkedIn profile for {person_name} at {company}. Then scrape that URL and return the profile details. ``` ## Common pitfalls Bad → Fix pairs grounded in observed integration failures. Each pair targets a single decision: rewrite the prompt, not the surrounding code. ```text Bad theme={"system"} How to estimate Total SA's annual IT spend? ``` ```text Fix theme={"system"} Find Total SA's annual reports and IT-services contracts that mention IT spend. For each source, extract the disclosed IT-spend figure and the year, with the citation URL. ``` ```text Bad theme={"system"} Tell me about the company linkup.so. ``` ```text Fix theme={"system"} Find the linkup.so homepage, product pages, and about page. Extract: what the company does, target customers, pricing model, and known investors. ``` Use `fromDate` / `toDate` instead of embedding date ranges in the query. ```text Bad theme={"system"} What AI funding rounds happened between 2025-01-01 and 2025-03-31? ``` ```text Fix theme={"system"} List European AI seed rounds. (with fromDate=2025-01-01, toDate=2025-03-31) ``` `"flash"` and `"fast"` are keyword-only and ignore everything beyond the literal query string. ```text Bad theme={"system"} First find Datadog's pricing page. Then scrape it. Then return plan names. (depth: flash) ``` ```text Fix theme={"system"} First find Datadog's pricing page. Then scrape it. Then return plan names. (depth: deep) ``` [**Fetch**](/pages/documentation/endpoints/fetch/overview) does not authenticate, so LinkedIn URLs return the login-wall view an anonymous visitor would see. Route LinkedIn URLs through **Search** instead — see [LinkedIn data extraction](#linkedin-data-extraction) above. ```text Bad theme={"system"} POST /fetch { "url": "https://www.linkedin.com/in/{slug}", "renderJs": true } ``` ```text Fix theme={"system"} POST /search { "query": "https://www.linkedin.com/in/{slug}\nReturn the profile details.", "depth": "standard" } ``` ## Resources Parameters, modes, output types, pricing, and the minimal first call. Domain and date filtering: how to combine `includeDomains`, `excludeDomains`, `fromDate`, and `toDate`. JSON-schema mechanics for `outputType` set to `"structured"`. Rewrite a draft prompt into the retrieval-shaped form the **Search** endpoint expects. Templates available. Errors and rate limits: [Errors](/pages/documentation/platform/errors) · [Rate limits](/pages/documentation/platform/rate-limits) # Search for AI agents Source: https://docs.linkup.so/pages/documentation/endpoints/search/for-agents Self-contained /search integration prompt for coding agents (Cursor, Claude Code, etc.) and tool-using LLMs. This page is structured for direct use as integration context for a coding agent, or as a function-calling tool definition. It is self-contained: operational guidance is repeated inline. **For coding agents reading this:** the page below is the integration prompt. Adapt the example to the project's language and pass it through as needed. *** # Linkup `/search` integration guide You are integrating the **Linkup `/search` API**: synchronous web search that returns ranked sources, sourced answers, or structured JSON. Four modes (`"flash"`, `"fast"`, `"standard"`, `"deep"`), three output types (`"searchResults"`, `"sourcedAnswer"`, `"structured"`). Latency: \<200 ms–\~30s depending on depth. ## When to use it Use `/search` when the model requires current facts, specific entities, or web content to ground a response. The endpoint performs a single synchronous round-trip: a natural-language query returns ranked results, optionally synthesized into an answer or a JSON object matching a caller-provided schema. Other endpoints in the API: * [**Fetch**](/pages/documentation/endpoints/fetch/overview) (`/fetch`): when the URL is already known. Pass `schema` for typed JSON from that page. * [**Research**](/pages/documentation/endpoints/research/overview) (`/research`): autonomous research agent. Async, 2–20 minutes depending on depth. * [**Tasks**](/pages/documentation/endpoints/tasks/overview) (`/tasks`): asynchronous batch wrapper around **Search**, **Fetch**, and **Research**. ## Setup ```bash theme={"system"} pip install linkup-sdk # Python # or npm install linkup-sdk # TypeScript ``` ```bash theme={"system"} export LINKUP_API_KEY="your-api-key" # treat like a password ``` ## Example (Python; adapt to the project's language) ```python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") # depth: "flash" | "fast" | "standard" | "deep" # - flash: <200 ms, keyword-only, no LLM, no scraping, no chaining # - fast: ~1s, higher-quality one-shot, keyword-only, no LLM, no scraping # - standard: single-iteration agentic, ~1-3s, can scrape ONE URL provided in the query, parallel sub-searches OK # - deep: up to 10 iterations, ~5-30s, can scrape multiple URLs, supports sequential search->scrape chains # Use `deep` when the next step depends on the previous step's output. # # outputType: # - searchResults: array of {name, url, content} — best for LLM grounding # - sourcedAnswer: natural language + sources — best for end-user display # - structured: JSON matching structuredOutputSchema — best for pipelines response = client.search( query="What is Microsoft's 2024 revenue?", depth="standard", output_type="sourcedAnswer", ) print(response) ``` ## Tool definition (OpenAI function-calling format) Register this as a tool the model can call. Remove the `"type": "function"` envelope and rename `parameters` to `input_schema` for the Anthropic format. ```json theme={"system"} { "type": "function", "function": { "name": "linkup_search", "description": "Searches the live web with an agentic search engine. Returns ranked sources, a sourced natural-language answer, or structured JSON. Use whenever the model needs current facts, specific named entities, recent events, or information that likely isn't in training data.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "Natural-language question or instruction. Be specific. Include the key entity, date range, or domain when known. For chained work (search then scrape), specify the order: 'First find X. Then scrape X. Then extract Y.'" }, "depth": { "type": "string", "enum": ["flash", "fast", "standard", "deep"], "description": "flash: <200 ms keyword search, no LLM, no scraping. fast: ~1s higher-quality one-shot, no LLM, no scraping. standard: single-iteration agentic, ~1-3s, can scrape one URL given in the query. deep: up to 10 iterations, ~5-30s, can scrape multiple URLs, supports sequential search-then-scrape chains. Use deep when the next step depends on the previous step's output." }, "outputType": { "type": "string", "enum": ["searchResults", "sourcedAnswer", "structured"], "description": "searchResults for LLM grounding. sourcedAnswer for end-user display. structured for pipelines (requires structuredOutputSchema)." }, "structuredOutputSchema": { "type": "string", "description": "Required when outputType=structured. A JSON Schema (as a string) the response must match. Root must be type 'object'. Keep schemas shallow." }, "includeDomains": { "type": "array", "items": { "type": "string" }, "description": "Restrict the search to up to 100 domains. Recommended when trusted sources for the use case are known." }, "excludeDomains": { "type": "array", "items": { "type": "string" }, "description": "Domains to exclude from results." }, "fromDate": { "type": "string", "description": "ISO 8601 (YYYY-MM-DD). Restrict to results on or after this date. Prefer this over embedding dates in the query." }, "toDate": { "type": "string", "description": "ISO 8601 (YYYY-MM-DD). Restrict to results on or before this date." } }, "required": ["query", "depth", "outputType"] } } } ``` ## Operational guidance (inline, in case other context is unavailable) ### Depth selection | Use case | Recommended setting | | -------------------------------------------------------- | --------------------------------------------------------------- | | Lowest latency | `"flash"` | | Single keyword lookup at \~1s | `"fast"` | | Single-pass retrieval comparable to one Google search | `"standard"` | | Breadth across adjacent keywords | `"standard"` with "run several searches with adjacent keywords" | | Scrape one URL provided by the user | `"standard"` | | Scrape several known URLs (no additional search per URL) | concurrent `"standard"` calls, each with one URL in the query | | Scrape several known URLs and run several searches | `"deep"` | | Locate a URL, then scrape it | `"deep"` | | Iterative search-then-scrape-then-search chains | `"deep"` | | Workload requirements undetermined | `"deep"` | `"standard"` accepts a URL in the query and will scrape it. Issuing several `"standard"` calls in parallel is an efficient way to combine search and scrape across multiple known pages. Use `"deep"` when the next step's behavior depends on what the previous step found. ### Query patterns * **Keyword-style** for `"flash"`, `"fast"`, and simple `"standard"`: `"NVIDIA Q4 2024 revenue"`. * **Instruction-style** for complex `"standard"` and all `"deep"`: `"Find Datadog's pricing page. Scrape it. Extract plan names, per-host prices, and included features."` * Queries should describe what the search must **find**, not what the model should **conclude**. Reasoning happens on the calling side. * Include context: dates, locations, industries, domains. * For chained work, specify the order: "First find X. Then scrape X. Then extract Y." ### Output type * `"searchResults"` when the model will reason over the sources. * `"sourcedAnswer"` when a user sees the answer directly. * `"structured"` when code parses the output. Keep schemas shallow. ### Constraints * Use `"standard"` or `"deep"` for instruction-style prompts; `"flash"` and `"fast"` are keyword-only. * A single `"standard"` call scrapes at most one URL. For several known URLs, issue concurrent `"standard"` calls; for sequential chains, use `"deep"`. * Set date ranges via `fromDate` / `toDate` rather than embedding dates in the query. * Always provide a `structuredOutputSchema` when `outputType` is `"structured"`. ## TypeScript notes * Import: `import { LinkupClient } from 'linkup-sdk'`. * Method: `await client.search({ query, depth, outputType })`. Single object argument. * Field names are camelCase in the SDK (`outputType`, `includeDomains`, `fromDate`). * Wrap in an `async` function and `await` the call. ## Links * [Search overview](/pages/documentation/endpoints/search/overview) * [Search best practices](/pages/documentation/endpoints/search/best-practices) * [API reference](/pages/documentation/endpoints/search/reference) * [Filtering tutorial](/pages/documentation/tutorials/filtering) * [Structured output tutorial](/pages/documentation/tutorials/structured-output-guide) * [OpenAPI spec](https://api.linkup.so/v1/openapi.json) * [Python SDK](https://pypi.org/project/linkup-sdk/) · [TypeScript SDK](https://www.npmjs.com/package/linkup-sdk) # Search overview Source: https://docs.linkup.so/pages/documentation/endpoints/search/overview Agentic web search with four depths and three output types: sources and snippets, sourced answers, or structured JSON. **Search** is Linkup's synchronous web search endpoint, optimized for AI consumption. Given a natural-language query, the `/search` route returns ranked sources, optionally bundled with a cited natural-language answer or with structured output matching a JSON schema. `depth` controls how much search and retrieval work the agent performs, and `outputType` selects the shape of the response. ## Modes **Search** comes with four `depth` modes: | Mode | Description | Latency | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `"flash"` | Lowest latency. Ranked sources and snippets from our index, no query reinterpretation, no LLM. | \<200 ms | | `"fast"` | Higher-quality one-shot retrieval. Query passed as-is to our index; no agentic planning. | \~1s | | `"standard"` | Single-iteration agentic search. The agent interprets the query, can run parallel sub-searches, and can scrape one URL provided in the query. | 1–3s | | `"deep"` | Multi-iteration agentic search-and-scrape chaining with evaluation. | 5–30s | ## Agentic search (`"standard"`, `"deep"`) In `"standard"` and `"deep"`, the system interprets the query, plans the retrieval, and evaluates the outputs returned. This means those two modes understand instructions on how to behave/perform the search. `"flash"` and `"fast"` do not invoke an LLM for retrieval. No query interpretation, no scraping — the query is passed as-is to the index. Depending on the prompt and the selected depth, the agent may: * run one or several web searches, * open and scrape webpages, * reuse information discovered in earlier steps, * refine or expand queries until the requested data is found. Instructions inside the query are followed literally. The prompt below is executed step by step: ```text theme={"system"} First, find the official website of the AI company linkup, then scrape the page and return the full content ``` For more information, read [search best practices](/pages/documentation/endpoints/search/best-practices). ## Output types | Output Type | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `"searchResults"` | Array of `{name, url, content}`. Ranked URLs and content snippets for grounding. | | `"sourcedAnswer"` | Search results plus a natural-language answer with inline citations. | | `"structured"` | Search results plus JSON matching the caller's `structuredOutputSchema`. Requires a JSON schema in `structuredOutputSchema`; see the [structured output tutorial](/pages/documentation/tutorials/structured-output-guide). | ## Filtering and date controls * `includeDomains` / `excludeDomains`: restrict to or exclude up to 100 domains. * `fromDate` / `toDate`: restrict to an ISO 8601 date range. * `maxResults`: cap the number of returned sources. * `includeImages`: surface relevant images. See the [domain and date filtering tutorial](/pages/documentation/tutorials/filtering) for examples. ## Pricing Cost depends on both `depth` and `outputType`. `"sourcedAnswer"` and `"structured"` invoke an LLM and carry a small premium over raw `"searchResults"`. | `depth` | `outputType` | Cost | | ----------------------------------- | ---------------------------------- | ---------------- | | `"flash"` / `"fast"` / `"standard"` | `"searchResults"` | \$0.005 per call | | `"flash"` / `"fast"` / `"standard"` | `"sourcedAnswer"` / `"structured"` | \$0.006 per call | | `"deep"` | `"searchResults"` | \$0.05 per call | | `"deep"` | `"sourcedAnswer"` / `"structured"` | \$0.055 per call | ## Example Create a Linkup account for free to get your API key. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") response = client.search( query="What is Microsoft's 2024 revenue?", depth="standard", output_type="sourcedAnswer", ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const response = await client.search({ query: "What is Microsoft's 2024 revenue?", depth: 'standard', outputType: 'sourcedAnswer', }); console.log(response); ``` ```shell curl theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer $LINKUP_API_KEY" \ --data-urlencode "q=What is Microsoft's 2024 revenue?" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=sourcedAnswer" ``` Successful response, by `outputType`: ```json sourcedAnswer theme={"system"} { "answer": "Microsoft's revenue for fiscal year 2024 was $245.1 billion, reflecting a 16% increase from the previous year.", "sources": [ { "name": "Microsoft 2024 Annual Report", "url": "https://www.microsoft.com/investor/reports/ar24/index.html", "snippet": "Highlights from fiscal year 2024 compared with fiscal year 2023 included: Microsoft Cloud revenue increased 23% to $137.4 billion." } ] } ``` ```json searchResults theme={"system"} { "results": [ { "type": "text", "name": "Microsoft 2024 Annual Report", "url": "https://www.microsoft.com/investor/reports/ar24/index.html", "content": "Highlights from fiscal year 2024 compared with fiscal year 2023 included: Microsoft Cloud revenue increased 23% to $137.4 billion." } ] } ``` `"structured"` returns the JSON object described by `structuredOutputSchema`. ## Next Depth selection, query phrasing, schema design. Tool definitions and integration prompt for coding agents. Full parameter spec and response schema. # /search Source: https://docs.linkup.so/pages/documentation/endpoints/search/reference https://api.linkup.so/v1/openapi.json POST /v1/search The `/search` endpoint allows you to retrieve web content. Create a Linkup account for free to get your API key. For an explanation of `depth`, `outputType`, and the other parameters, see the [Search overview](/pages/documentation/endpoints/search/overview). # Tasks best practices Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/best-practices When to use /tasks, batch sizing, polling strategy, error handling, and cost considerations. This page covers when to reach for **Tasks** over the synchronous endpoints, how to size and poll batches, and how to handle errors, mixed-endpoint submissions, and the per-task cost model. ## When to use Tasks **Tasks** is an asynchronous wrapper. It does **not** make individual calls cheaper or faster than the synchronous endpoints. Reach for it when: * **Bulk workloads**: CRM enrichment, backfills, batch research over hundreds of queries. * **Long-running jobs**: submit and poll later, instead of holding an HTTP connection open. * **Scheduled pipelines**: kick off a nightly batch, retrieve results in the morning. * **Mixed batches**: combine [**Search**](/pages/documentation/endpoints/search/overview), [**Fetch**](/pages/documentation/endpoints/fetch/overview), and [**Research**](/pages/documentation/endpoints/research/overview) calls in one submission. * **Concurrency overflow**: the workload exceeds the synchronous concurrency budget. For interactive single-shot calls (chat UIs, agent steps), call the synchronous endpoints directly. End-to-end latency via **Tasks** is identical or slightly higher, plus the polling round-trip cost. ## Batch sizing The hard limit is 100 tasks per submission. For larger workloads, submit multiple batches in parallel. There is no penalty for doing so. ```python theme={"system"} # 500 queries → 5 batches of 100 batches = [queries[i:i+100] for i in range(0, len(queries), 100)] task_ids = [] for batch in batches: response = client.tasks.create([ {"type": "search", "input": {...}} for q in batch ]) task_ids.extend(t.id for t in response) ``` ## Polling strategy Two strategies are appropriate, depending on the workload: **Per-task polling** (when results must be processed as they arrive): ```python theme={"system"} import time for tid in task_ids: while True: result = client.tasks.get(tid) if result.status in ("completed", "failed"): break time.sleep(2) handle(result) ``` **Bulk polling** (when the workload can wait for the entire batch): ```python theme={"system"} import time remaining = set(task_ids) while remaining: all_tasks = client.tasks.list() for t in all_tasks: if t.id in remaining and t.status in ("completed", "failed"): handle(t) remaining.discard(t.id) if remaining: time.sleep(5) ``` Bulk polling consumes fewer API calls when the batch is large. Per-task polling provides faster latency-to-first-result. **Polling cadence:** * Mostly `"search"` (`"flash"`/`"fast"`/`"standard"`) and `"fetch"`: 1–2 second intervals. * Mostly `"research"`: 5 second intervals, with backoff to 30 seconds for long-running batches. * Mixed: start at 2 seconds, back off to 10. Maximum poll rate is 1 request per second. Higher rates trigger rate limits without reducing time-to-completion. ## Error handling Each task in a batch completes or fails independently. A failure in one task does **not** fail the batch. Inspect `error` on individual tasks and retry only the failures. ```python theme={"system"} for t in completed: if t.status == "failed": retry_inputs.append(t.input) else: handle(t.output) if retry_inputs: client.tasks.create([{"type": "search", "input": i} for i in retry_inputs]) ``` No credit is deducted for failed tasks (consistent with the synchronous endpoints). ## Mixed-endpoint batches A common pattern combines **Search** to find URLs and **Fetch** to read them in one batch. ```json theme={"system"} [ { "type": "search", "input": { "q": "Datadog pricing", "depth": "standard", "outputType": "searchResults" } }, { "type": "fetch", "input": { "url": "https://www.datadoghq.com/pricing/", "renderJs": true } }, { "type": "fetch", "input": { "url": "https://docs.datadoghq.com/account_management/billing/usage_metrics/", "renderJs": false } } ] ``` Submission order does not constrain execution order. Tasks run in parallel. For dependent work (search results feeding fetch URLs), submit the second batch after the first completes. ## Cost behavior **Tasks** does not change pricing. Each task is billed exactly as a direct synchronous call to its endpoint. **Tasks** is appropriate for workflow benefits, not cost reduction. Cost reduction is achieved by selecting the appropriate `depth` (`"standard"` over `"deep"` when the workload allows), using `includeDomains` to reduce work, and designing schemas to be shallow. ## Result lifetime Completed task results are retrievable for a bounded period. For long-running pipelines, persist results to durable storage as soon as they complete rather than relying on **Tasks** for storage. ## Common pitfalls Bad → Fix pairs grounded in the documented behavior of **Tasks** (per-task billing identical to direct calls, 1 rps poll cap, bounded result lifetime). **Polling each task individually when the batch is large.** Wastes API budget against the 1 rps cap. ```python Bad theme={"system"} for tid in task_ids: while True: result = client.tasks.get(tid) if result.status in ("completed", "failed"): break time.sleep(0.2) ``` ```python Fix theme={"system"} remaining = set(task_ids) while remaining: for t in client.tasks.list(): if t.id in remaining and t.status in ("completed", "failed"): handle(t) remaining.discard(t.id) if remaining: time.sleep(5) ``` **Expecting batching to reduce per-task cost.** Pricing is per-task identical to direct synchronous calls. ```python Bad theme={"system"} client.tasks.create([ {"type": "research", "input": {"q": q, "outputType": "sourcedAnswer"}} for q in queries ]) ``` ```python Fix theme={"system"} client.tasks.create([ {"type": "search", "input": {"q": q, "depth": "standard", "outputType": "sourcedAnswer", "includeDomains": TRUSTED_DOMAINS}} for q in queries ]) ``` **Persisting `taskId`s late** and losing results to the bounded result lifetime. ```python Bad theme={"system"} task_ids = [t.id for t in client.tasks.create(payload)] # ... process unrelated work for hours ... results = [client.tasks.get(tid) for tid in task_ids] ``` ```python Fix theme={"system"} task_ids = [t.id for t in client.tasks.create(payload)] db.save_task_ids(task_ids) # poll on a schedule, write each completed result to durable storage ``` ## Resources * [Tasks overview](/pages/documentation/endpoints/tasks/overview) * [Research best practices](/pages/documentation/endpoints/research/best-practices) * [Pricing](/pages/documentation/platform/pricing) * [Errors](/pages/documentation/platform/errors) * [Rate limits](/pages/documentation/platform/rate-limits) # Tasks for AI agents Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/for-agents Self-contained /tasks integration prompt for coding agents that need to run Linkup searches asynchronously in batch. This page is structured for direct use as integration context for a coding agent. Operational guidance is repeated inline so the page is self-contained. *** # Linkup `/tasks` integration guide You are integrating the **Linkup `/tasks` API**: an asynchronous batch wrapper around [**Search**](/pages/documentation/endpoints/search/overview), [**Fetch**](/pages/documentation/endpoints/fetch/overview), and [**Research**](/pages/documentation/endpoints/research/overview). A single submission accepts up to 100 tasks, in any mix of the three endpoints, with the same parameters and pricing as direct calls. There is no batching surcharge. ## When to use it Use **Tasks** for bulk and asynchronous workloads: CRM enrichment, backfills, scheduled pipelines, mixed-endpoint batches. * Hundreds of queries to process: **Tasks**. * Mixed **Search** + **Fetch** + **Research** workflow: **Tasks** (one submission). * Long-running research that should not block: **Tasks**. * Interactive single-shot calls: call the synchronous endpoint directly. Each task is billed exactly as a direct synchronous call to the same endpoint. No batching surcharge, no batching discount. ## Setup ```bash theme={"system"} pip install linkup-sdk # Python # or npm install linkup-sdk # TypeScript ``` ```bash theme={"system"} export LINKUP_API_KEY="your-api-key" ``` ## Example (Python; adapt to the project's language) ```python theme={"system"} import time from linkup import LinkupClient client = LinkupClient(api_key="") # Submit up to 100 tasks per call. Mix types freely. # Each task carries the same parameters as the corresponding synchronous endpoint. tasks = client.tasks.create([ { "type": "search", "input": { "q": "What is Microsoft's 2024 revenue?", "depth": "standard", "outputType": "sourcedAnswer", }, }, { "type": "fetch", "input": { "url": "https://docs.linkup.so", "renderJs": True, }, }, { "type": "research", "input": { "q": "Compare 2024 cloud revenue growth of MSFT, AMZN, and GOOG.", "outputType": "sourcedAnswer", "mode": "investigate", "reasoningDepth": "L", }, }, ]) # Poll. Errors are per-task; a failure does not fail the batch. remaining = {t.id for t in tasks} while remaining: for t in client.tasks.list(): if t.id in remaining and t.status in ("completed", "failed"): if t.status == "completed": print(t.id, t.output) else: print(t.id, "FAILED:", t.error) remaining.discard(t.id) if remaining: time.sleep(2) ``` ## Tool definition (OpenAI function-calling format) Remove the `"type": "function"` envelope and rename `parameters` to `input_schema` for the Anthropic format. Note that this tool is **async**: the handler should poll on the model's behalf and return completed results, not task ids. ```json theme={"system"} { "type": "function", "function": { "name": "linkup_tasks", "description": "Submits a batch of /search, /fetch, and /research tasks asynchronously. Up to 100 tasks per call. Use for bulk workloads, scheduled pipelines, or mixed-endpoint workflows. Same parameters and pricing as the synchronous endpoints. For interactive single-shot calls, call linkup_search / linkup_fetch / linkup_research directly.", "parameters": { "type": "object", "properties": { "tasks": { "type": "array", "maxItems": 100, "description": "Array of task objects to submit. Each task is { type, input }.", "items": { "type": "object", "properties": { "type": { "type": "string", "enum": ["search", "fetch", "research"], "description": "Which endpoint to invoke for this task." }, "input": { "type": "object", "description": "Parameters for the chosen endpoint. For 'search', same as POST /search. For 'fetch', same as POST /fetch. For 'research', same as POST /research." } }, "required": ["type", "input"] } } }, "required": ["tasks"] } } } ``` ## Operational guidance (inline) ### Appropriate workloads * Bulk workloads (hundreds of queries). * Asynchronous workflows that would otherwise hold an HTTP connection open. * Mixed-endpoint batches in one submission. ### Inappropriate workloads * Single-shot interactive calls. Use the synchronous endpoint directly. * Cost reduction. Pricing is per-task identical to direct calls. ### Submission * Up to 100 tasks per `POST /tasks` call. For more, submit parallel batches. * Tasks run in parallel; submission order does not constrain execution order. * For dependent work (search results feeding fetch URLs), submit the second batch after the first completes. ### Polling * Mostly `"search"` (`"flash"`/`"fast"`/`"standard"`) and `"fetch"`: 1–2 second intervals. * Mostly `"research"`: 5 second intervals, with backoff to 30 seconds for long-running batches. * Mixed: start at 2 seconds, back off to 10. * Maximum poll rate: 1 request per second. * Use `GET /tasks` (list) for bulk polling. Fewer API calls than per-task polling. ### Error handling * Each task succeeds or fails independently. * A failure in one task does not fail the batch. * Inspect `error` on failed tasks; retry only the failures. * No credit is deducted for failed tasks. ### Result storage * Completed task results are retrievable for a bounded period. * Persist to durable storage as soon as a task completes. ### Constraints * **Tasks** is not cheaper than direct calls. Pricing is per-task identical. * Polling above 1 request per second triggers rate limits. * **Tasks** is not durable result storage. ## TypeScript notes * Import: `import { LinkupClient } from 'linkup-sdk'`. * Methods: `await client.tasks.create([...])`, `await client.tasks.list()`, `await client.tasks.get(id)`. * `tasks.create` takes the array directly (not wrapped in `{ tasks: [...] }`). ## Links * [Tasks overview](/pages/documentation/endpoints/tasks/overview) * [Tasks best practices](/pages/documentation/endpoints/tasks/best-practices) * [API reference: POST /tasks](/pages/documentation/endpoints/tasks/post) * [API reference: GET /tasks](/pages/documentation/endpoints/tasks/list) * [API reference: GET /tasks/:id](/pages/documentation/endpoints/tasks/get) * [Pricing](/pages/documentation/platform/pricing) # /tasks/:id Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/get https://api.linkup.so/v1/openapi.json GET /v1/tasks/{id} The `GET /tasks/:id` method, with task identifier, retrieves a specific task information. Create a Linkup account for free to get your API key. This endpoint only retrieves data from an existing task. It does not create a new task. To create a new task, use [`POST /tasks`](/pages/documentation/endpoints/tasks/post). # /tasks Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/list https://api.linkup.so/v1/openapi.json GET /v1/tasks The `GET /tasks` method, without a task identifier, lists all created tasks for the authenticated organization, with pagination. Create a Linkup account for free to get your API key. This endpoint only retrieves data from existing tasks. It does not create a new task. To create a new task, use [`POST /tasks`](/pages/documentation/endpoints/tasks/post). # Tasks overview Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/overview Asynchronous batch wrapper around /search, /fetch, and /research. **Tasks** is Linkup's asynchronous batch wrapper around [**Search**](/pages/documentation/endpoints/search/overview), [**Fetch**](/pages/documentation/endpoints/fetch/overview), and [**Research**](/pages/documentation/endpoints/research/overview). A single submission to `/tasks` accepts up to 100 tasks, in any mix of the three endpoints, with the same parameters and pricing as direct calls. There is no batching surcharge. No batching surcharge means **Tasks** is useful even for 2–3 calls when one polling loop is simpler than several synchronous ones. **Tasks** is appropriate for bulk and scheduled workloads. Guidance on when to reach for **Tasks** versus the synchronous endpoints lives on the [Tasks best practices](/pages/documentation/endpoints/tasks/best-practices#when-to-use-tasks) page. ## How it works 1. `POST /tasks` with an array of `{ type, input }` objects. Returns task identifiers immediately. 2. Poll `GET /tasks/{id}` for a specific task, or `GET /tasks` to list all tasks for the account. 3. While running, `status` is `"pending"` or `"processing"`. On completion, read the `output` field. On failure, inspect `error`. Maximum 100 tasks per submission. Larger workloads must be split across multiple submissions. ## Parameters per task type Each task carries the same parameters as the corresponding synchronous endpoint: | `type` | `input` parameters | Reference | | ------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | `"search"` | Same as `/search`: `q`, `depth`, `outputType`, `includeDomains`, ... | [POST /search](/pages/documentation/endpoints/search/reference) | | `"fetch"` | Same as `/fetch`: `url`, `mode`, `renderJs`, `includeRawHtml`, `extractImages`, `schema`, `instructions` | [POST /fetch](/pages/documentation/endpoints/fetch/reference) | | `"research"` | Same as `/research`: `q`, `outputType`, `mode`, `reasoningDepth`, ... | [POST /research](/pages/documentation/endpoints/research/post) | Tasks in a single submission can mix `"search"`, `"fetch"`, and `"research"` freely — each carries its own parameters and is billed at its own rate. ## Pricing Each task is billed exactly as a direct synchronous call to the same endpoint. No batching surcharge, no batching discount. | Task type | Cost | | ------------------------------------------------------------------------------ | ------------------------------------- | | `"search"` (`"flash"`/`"fast"`/`"standard"`, `"searchResults"`) | \$0.005 | | `"search"` (`"flash"`/`"fast"`/`"standard"`, `"sourcedAnswer"`/`"structured"`) | \$0.006 | | `"search"` (`"deep"`, `"searchResults"`) | \$0.05 | | `"search"` (`"deep"`, `"sourcedAnswer"`/`"structured"`) | \$0.055 | | `"fetch"` (`mode` = `"standard"`, no JS) | \$0.001 | | `"fetch"` (`mode` = `"standard"`, with JS) | \$0.005 | | `"fetch"` (`mode` = `"pro"`, no JS) | \$0.005 | | `"fetch"` (`mode` = `"pro"`, with JS) | \$0.01 | | `"fetch"` with `schema` | Add \$0.001 to the matching row above | | `"research"` (`"S"`) | \$0.25 | | `"research"` (`"M"`) | \$0.50 | | `"research"` (`"L"`) | \$1.50 | | `"research"` (`"XL"`) | \$2.50 | ## Example Create a Linkup account for free to get your API key. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") tasks = client.tasks.create([ { "type": "search", "input": { "q": "What is Microsoft's 2024 revenue?", "depth": "deep", "outputType": "sourcedAnswer", "includeDomains": ["microsoft.com", "agolution.com"], "excludeDomains": ["wikipedia.org"], }, }, { "type": "fetch", "input": { "url": "https://docs.linkup.so", "mode": "standard", "renderJs": False, }, }, { "type": "research", "input": { "q": "Compare 2024 cloud revenue growth of Microsoft, Amazon, and Google.", "outputType": "sourcedAnswer", "mode": "investigate", "reasoningDepth": "L", }, }, ]) for t in tasks: print(t.id, t.type, t.status) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '' }); const tasks = await client.tasks.create([ { type: 'search', input: { q: "What is Microsoft's 2024 revenue?", depth: 'deep', outputType: 'sourcedAnswer', includeDomains: ['microsoft.com', 'agolution.com'], excludeDomains: ['wikipedia.org'], }, }, { type: 'fetch', input: { url: 'https://docs.linkup.so', mode: 'standard', renderJs: false, }, }, { type: 'research', input: { q: 'Compare 2024 cloud revenue growth of Microsoft, Amazon, and Google.', outputType: 'sourcedAnswer', mode: 'investigate', reasoningDepth: 'L', }, }, ]); for (const t of tasks) { console.log(t.id, t.type, t.status); } ``` ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/tasks \ --header "Authorization: Bearer $LINKUP_API_KEY" \ --header 'Content-Type: application/json' \ --data '[ { "type": "search", "input": { "q": "What is Microsoft'\''s 2024 revenue?", "depth": "deep", "outputType": "sourcedAnswer", "includeDomains": ["microsoft.com", "agolution.com"], "excludeDomains": ["wikipedia.org"] } }, { "type": "fetch", "input": { "url": "https://docs.linkup.so", "mode": "standard", "renderJs": false } }, { "type": "research", "input": { "q": "Compare 2024 cloud revenue growth of Microsoft, Amazon, and Google.", "outputType": "sourcedAnswer", "mode": "investigate", "reasoningDepth": "L" } } ]' ``` `POST /v1/tasks` returns an array of task envelopes immediately, with `status` of `"pending"` and `output` of `null`. Poll `GET /v1/tasks/{id}` (or `GET /v1/tasks` for bulk listing) to retrieve completed results: ```json theme={"system"} [ { "id": "01234-abcd-56789", "type": "search", "status": "pending", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z", "error": null, "input": { "q": "What is Microsoft's 2024 revenue?", "depth": "deep", "outputType": "sourcedAnswer" }, "output": null }, { "id": "01234-abcd-56790", "type": "fetch", "status": "pending", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z", "error": null, "input": { "url": "https://docs.linkup.so", "mode": "standard", "renderJs": false }, "output": null } // ... one envelope per submitted task ] ``` Once `status` is `"completed"`, `output` is populated with the same shape as the corresponding synchronous endpoint's response. On `"failed"`, `error` carries a string explanation. ## Next Batch sizing, polling strategy, error handling. Tool definition and integration prompt. Full parameter spec and response schema. # /tasks Source: https://docs.linkup.so/pages/documentation/endpoints/tasks/post https://api.linkup.so/v1/openapi.json POST /v1/tasks The `/tasks` endpoint allows you to run Linkup's `/search`, `/fetch`, and `/research` calls in batch and asynchronously. Create a Linkup account for free to get your API key. This is an **asynchronous** endpoint. It immediately returns the tasks' identifiers, before they complete. Use [`GET /v1/tasks/:id`](/pages/documentation/endpoints/tasks/get) to poll for a specific task, or [`GET /v1/tasks`](/pages/documentation/endpoints/tasks/list) to list all your tasks. For batch sizing, polling strategies, and example workloads, see the [Tasks overview](/pages/documentation/endpoints/tasks/overview). # For AI agents Source: https://docs.linkup.so/pages/documentation/get-started/for-agents Download the Linkup knowledge pack for agents: what you can build, which API to call, and how to write optimized queries and workflows. Learn how to build with Linkup by pasting this prompt to your agent: ```text theme={"system"} Read https://github.com/LinkupPlatform/linkup-for-agents and follow the Linkup agent instructions to set up Linkup for this project. ``` ## Use the knowledge pack **1. Clone the repo — for one project, or to learn Linkup yourself.** Point your agent at `AGENTS.md`; it routes itself. Every knowledge file and recipe is there to read and adapt. ```bash theme={"system"} git clone https://github.com/LinkupPlatform/linkup-for-agents.git ``` **2. Install it as skills — recommended for coding agents.** Auto-loads whenever a task involves search, fetch, research, extraction, or workflow design. Add `-g` to cover all your projects. ```bash theme={"system"} npx skills add LinkupPlatform/skills ``` ## Connect Linkup Your product can call Linkup through the [SDKs](/pages/sdk/python/python) or plain HTTP — no MCP required. Set `LINKUP_API_KEY` first; get one at [app.linkup.so](https://app.linkup.so). The [MCP server](/pages/integrations/mcp/mcp) is optional: install it when you want your coding agent to call **Search**, **Fetch**, and **Research** directly, or to live-test queries while building. Give your assistant live Linkup tools inside Claude Code, Cursor, VS Code, Codex, and other MCP clients. LangChain, CrewAI, LlamaIndex, Vercel AI SDK, Agno, Composio, and more — Linkup ships as a tool you can register. OpenClaw and other agent platforms with Linkup pre-wired. ## Our APIs and links Linkup exposes **Search**, **Fetch**, **Research**, **Extract**, and **Tasks** — see [the endpoints overview](/pages/documentation/get-started/introduction#our-endpoints) for what each does. Per-endpoint `for-agents` briefs carry copy-pasteable tool definitions: * [Endpoints overview](/pages/documentation/get-started/introduction#our-endpoints) * [Search for agents](/pages/documentation/endpoints/search/for-agents) * [Fetch for agents](/pages/documentation/endpoints/fetch/for-agents) * [Research for agents](/pages/documentation/endpoints/research/for-agents) * [Extract for agents](/pages/documentation/endpoints/extract/for-agents) * [Tasks for agents](/pages/documentation/endpoints/tasks/for-agents) * [OpenAPI spec](https://api.linkup.so/v1/openapi.json) # Introduction Source: https://docs.linkup.so/pages/documentation/get-started/introduction The production-grade web search API for AI. Learn how to build with Linkup by pasting this prompt to your agent: ```text theme={"system"} Read https://github.com/LinkupPlatform/linkup-for-agents and follow the Linkup agent instructions to set up Linkup for this project. ``` Linkup is the production-grade web search API for AI. Teams at Legora, Polymarket, Cohere, McKinsey, and SNCF ship production AI on Linkup because four things have to be true: * **Accuracy.** [**Search**](/pages/documentation/endpoints/search/overview) ranks #1 among sub-second web search APIs on Verified SimpleQA with a 94% F-score. [**Research**](/pages/documentation/endpoints/research/overview) ranks #1 across the board on SealQA-0 with 61% accuracy. The [evaluation harness](https://github.com/LinkupPlatform/eval-simpleQA) is open source. * **Customization.** The index adapts to your domain. Define which sources to trust, which domains to prefer, which proprietary data to blend in. * **Security.** SOC 2 Type II, Zero Data Retention, GDPR, and Single Sign-On on every tier. Bring Your Own Cloud is available for the highest data sovereignty requirements: the full Linkup index runs inside your VPC and queries never leave your environment. * **Latency.** [**Search**](/pages/documentation/endpoints/search/overview) starts under 200 ms. Fast enough for voice AI, sales copilots, and live research tools. ## Our endpoints Linkup exposes five endpoints. Each has a distinct shape and is tuned for a different point on the latency/quality curve. | Endpoint | Shape | Latency | Mode | Use when | | ---------------------------------------------------------------- | ----------------------------------------------------------------- | -------------- | ----- | ------------------------------------------------------------------------- | | [**Search**](/pages/documentation/endpoints/search/overview) | Query → ranked URLs, sourced answer, or structured JSON | \<200 ms–\~30s | sync | Grounding an agent or LLM answer with retrieved web context | | [**Fetch**](/pages/documentation/endpoints/fetch/overview) | URL → clean LLM-ready markdown, optionally typed JSON | \~1s | sync | Extracting the full content of a known page, or typed JSON from that page | | [**Research**](/pages/documentation/endpoints/research/overview) | Query → detailed report or precise answer with inline citations | 2–20 min | async | Multi-source synthesis, full reports, precise answers | | [**Tasks**](/pages/documentation/endpoints/tasks/overview) | Batch of up to 100 Search/Fetch/Research calls → results for each | 1s–20 min | async | Bulk, scheduled, or background workloads | | [**Extract**](/pages/documentation/endpoints/extract/overview) | Seed URL + query → downloadable structured rows | Variable | async | Repeated records on a known page, such as products, jobs, or directories | **Extract** is in closed beta and access is granted by request. Behavior and parameters may change. ## Where to next Make your first call in five minutes. Tool definitions and integration prompts for coding agents. Test Linkup in the browser. Full parameter spec for every endpoint. Query precision is the biggest lever on result quality. Read the [Search best practices](/pages/documentation/endpoints/search/best-practices) page or use the [Prompt Optimizer](https://prompt.linkup.so). # Quickstart Source: https://docs.linkup.so/pages/documentation/get-started/quickstart Integrate with Linkup in 5 minutes The Linkup API can be used in AI workflows to find and access high-quality content from the internet. Follow the steps below to integrate Linkup in a few minutes. Create a Linkup account for free to get your API key. ```python python theme={"system"} pip install linkup-sdk ``` ```js js theme={"system"} npm i linkup-sdk ``` ```shell cli theme={"system"} npm install -g linkup-search-cli linkup setup ``` Retrieve context to ground your LLM's answer. This is RAG over the live internet. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") response = client.search( query="What is Microsoft's 2024 revenue?", depth="deep", output_type="sourcedAnswer" ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '', }); const askLinkup = async () => { return await client.search({ query: "What is Microsoft's 2024 revenue?", depth: 'deep', outputType: 'sourcedAnswer', }); }; askLinkup().then(console.log); ``` ```shell curl theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer $LINKUP_API_KEY" \ --data-urlencode "q=What is Microsoft's 2024 revenue?" \ --data-urlencode "depth=deep" \ --data-urlencode "outputType=sourcedAnswer" ``` ```shell cli theme={"system"} linkup --json search "What is Microsoft's 2024 revenue?" --depth deep ``` Response: ```json 200 [expandable] theme={"system"} { "answer": "Microsoft's revenue for fiscal year 2024 was $245.1 billion, reflecting a 16% increase from the previous year.", "sources": [ { "name": "Microsoft 2024 Annual Report", "url": "https://www.microsoft.com/investor/reports/ar24/index.html", "snippet": "Highlights from fiscal year 2024 compared with fiscal year 2023 included: Microsoft Cloud revenue increased 23% to $137.4 billion.\nMore broadly, we continued to see sustained revenue growth from migrations as customers turn to Azure. Azure Arc is helping customers streamline their transition, as they secure, develop, and operate workloads with Azure services anywhere. We have 36,000 Arc customers, up 90 percent year-over-year.\nWith our acquisition of Activision Blizzard King, which closed October 2023, we’ve added hundreds of millions of players to our ecosystem. We now have 20 franchises that have generated over $1 billion in lifetime revenue—from Candy Crush, Diablo, and Halo, to Warcraft, Elder Scrolls, and Gears of War.\nGrowth depends on our ability to reach new users in new markets such as frontline workers, small and medium businesses, and growth markets, as well as add value to our core product and service offerings to span AI and productivity categories such as communication, collaboration, analytics, security, and compliance. Office Commercial revenue is mainly affected by a combination of continued installed base growth and average revenue per user expansion, as well as the continued shift from Office licensed on-premises to Office 365.\nGrowth depends on our ability to reach new users, add value to our core product set with new features including AI tools, and continue to expand our product and service offerings into new markets. Office Consumer revenue is mainly affected by the percentage of customers that buy Office with their new devices and the continued shift from Office licensed on-premises to Microsoft 365 Consumer subscriptions.", "favicon": "https://www.microsoft.com/favicon.ico" }, { "name": "Microsoft's Financial Results in FY24 Q4 – AGOLUTION", "url": "https://agolution.com/en/microsoft/financial-reporting/2024-q4/", "snippet": "What did the other quarterly figures look like and how did Microsoft fare in fiscal year 2024 as a whole? Microsoft’s revenue amounted to $64.7 billion - and increased by 15%.\nWhat did the other quarterly figures look like and how did Microsoft fare in fiscal year 2024 as a whole? Microsoft’s revenue amounted to $64.7 billion - and increased by 15%.\nThe Xbox and Gaming segment recorded a remarkable jump in revenue of 61%, with the majority of this increase being due to the acquisition of Activision Blizzard King by Microsoft at the end of last year. Since then, Microsoft has owned popular video games such as “Call of Duty”, “Overwatch” and “Candy Crush”. The results for Microsoft’s fiscal year 2024 as compared to fiscal year 2023 were as follows.\nMicrosoft achieved total revenue of $245.1 billion, an increase of 16%. The operating income amounted to $109.4 billion and increased by 24%. Total net income amounted to $88.1 billion and increased by 22%. Earnings per share amounted to $11.8 - here too there was an increase of 22%. Solid fiscal year 2024: Microsoft remains one of the market leaders in the era of AI.\nThe third quarter of Microsoft’s 2024 fiscal year was again characterized by strong cloud results.", "favicon": "https://agolution.com/favicon.ico" }, { "name": "Microsoft Revenue 2010-2024 | MSFT | MacroTrends", "url": "https://www.macrotrends.net/stocks/charts/MSFT/microsoft/revenue", "snippet": "Microsoft revenue for the quarter ending December 31, 2024 was $69.632B, a 12.27% increase year-over-year.\nMicrosoft annual/quarterly revenue history and growth rate from 2010 to 2024. Revenue can be defined as the amount of money a company receives from its customers in exchange for the sales of goods or services. Revenue is the top line item on an income statement from which all costs and expenses are subtracted to arrive at net income.\nMicrosoft revenue for the quarter ending December 31, 2024 was $69.632B, a 12.27% increase year-over-year.\nMicrosoft annual revenue for 2023 was $211.915B, a 6.88% increase from 2022. Microsoft annual revenue for 2022 was $198.27B, a 17.96% increase from 2021.", "favicon": "https://www.macrotrends.net/favicon.ico" }, ... ] } ``` That's it — you've made your first authenticated call to Linkup. Switch `depth` to `"flash"` for under 200 ms latency, or keep `"deep"` for multi-iteration search-and-scrape chaining. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). Depth selection, query phrasing, schema design. The biggest lever on result quality is the prompt itself. Drop-in integration prompt and per-endpoint briefs for using Linkup from coding agents and tool-using LLMs. # Authentication Source: https://docs.linkup.so/pages/documentation/platform/authentication Authenticate with the Linkup API Create a Linkup account for free to get your API key. **No API key?** You can also access the Linkup API using the [x402 payment protocol](/pages/documentation/platform/x402): pay per request in USDC, no account needed. ## Using cURL Your API key needs to be sent with every request as a Bearer token in the `Authorization` header. ```shell curl theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer " \ ... ``` ## Using the Python SDK **Option 1:** Set a `LINKUP_API_KEY` environment variable in your shell before using the SDK. ```shell shell theme={"system"} export LINKUP_API_KEY='' ``` **Option 2:** Set the `LINKUP_API_KEY` environment variable directly within Python, using for instance `os.environ` or [python-dotenv](https://github.com/theskumar/python-dotenv). ```python python theme={"system"} import os from linkup import LinkupClient os.environ["LINKUP_API_KEY"] = "" # or dotenv.load_dotenv() client = LinkupClient() ... ``` **Option 3**: Directly pass the Linkup API key to the Linkup Client. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") ... ``` ## Using the JS SDK Pass the Linkup API key to the Linkup Client. ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '', }); ``` ## Using the CLI The CLI can store your API key locally with the interactive setup flow: ```shell shell theme={"system"} linkup setup ``` For scripts and CI, set `LINKUP_API_KEY` in the environment. The environment variable takes precedence over the local config file at `~/.linkup/config`. ```shell shell theme={"system"} LINKUP_API_KEY='' linkup search "What is Microsoft's 2024 revenue?" ``` **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Errors Source: https://docs.linkup.so/pages/documentation/platform/errors Linkup API errors and how to handle them This guide includes an overview of error codes you might see from both the API and our SDKs. ## API errors Whatever the HTTP error code is, the response payload will contain the following object: **`statusCode`**: number - HTTP error code. **`error`**: object - Linkup error details containing: * **`code`**: string - Error name * **`message`**: string - Description of the error * **`details`**: array - Details of the error. This array may be empty or include objects with the following properties: * **`field`**: string - The field that caused the error * **`message`**: string - A description of the field error Here is an example: ```json theme={"system"} { "statusCode": 400, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [ { "field": "outputType", "message": "outputType must be one of the following values: sourcedAnswer, searchResults, structured" } ] } } ``` **API error codes** | Code | Possible Reasons | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` - Bad Request |
  • Required parameter is missing
  • Invalid parameter
  • Search query yields no result
  • Fetch request targets an HTML page > 20MB or a PDF > 100MB
  • Fetch request returned an unsupported content type
  • Fetch target URL was not found
  • Fetch target URL is unreachable (connection failed or timed out)
  • Fetch `instructions` sent without `schema`
  • Fetch `schema` is not a JSON Schema object
  • Fetch structured extraction failed after the page was fetched
| | `401` - Unauthorized | API Key is missing or invalid | | `402` - Payment Required | x402 payment signature is missing, invalid, or settlement failed. See [x402 docs](/pages/documentation/platform/x402) | | `403` - Forbidden | API key does not have permission to access this resource | | `409` - Conflict | Resource conflict (e.g., duplicate entry or conflicting request) | | `429` - Too Many Requests | You have run out of credit or you are sending too many concurrent requests | | `500` - Internal Server Error | Something's up on our end | | `504` - Gateway Timeout | Request deadline exceeded | ## SDK errors **Python & JS SDK error types** | Type | Reason | | ---------------------------------------- | ----------------------------------------------------------------------------------------- | | `LinkupInvalidRequestError` | Required parameter is missing, or invalid parameter | | `LinkupNoResultError` | Search query yields no result | | `LinkupAuthenticationError` | API Key is missing, invalid or you do not have permission to access this resource | | `LinkupInsufficientCreditError` | You have run out of credit | | `LinkupTooManyRequestsError` | You are sending too many concurrent requests | | `LinkupFetchError` | The provided URL can't be fetched | | `LinkupFetchResponseTooLargeError` | The provided URL's response is too large to be processed (>20MB for HTML, >100MB for PDF) | | `LinkupFetchTargetNotFoundError` | The target URL was not found | | `LinkupFetchTargetUnreachableError` | The target URL could not be reached (connection failed or timed out) | | `LinkupFetchUnsupportedContentTypeError` | The URL returned a content type that cannot be processed (e.g. binary, media) | | `LinkupUnknownError` | Anything else | # Pricing Source: https://docs.linkup.so/pages/documentation/platform/pricing How the Linkup API pricing system works ## Billing When you first sign up with a professional email address, your account is automatically credited with \$20. We will top up eligible accounts back to \$20 each month. You can add money to your account in the [Billing](https://app.linkup.so/organization/billing) section of the Linkup app. If you top up your account with \$1,000 or more, we will add free credits to your account. Please reach out to [support@linkup.so](mailto:support@linkup.so) for custom needs. Each time you make a successful request to our API endpoints, an amount is subtracted from your account. The amount deducted per call depends on the endpoint and parameters: ## Search endpoint For the [**Search**](/pages/documentation/endpoints/search/overview) endpoint, the cost depends on both the `depth` and `outputType` parameters. `"sourcedAnswer"` and `"structured"` invoke an LLM to compose or shape the response and carry a small premium over raw `"searchResults"`. | `depth` | `outputType` | Cost | | ----------------------------------- | ---------------------------------- | ------- | | `"flash"` / `"fast"` / `"standard"` | `"searchResults"` | \$0.005 | | `"flash"` / `"fast"` / `"standard"` | `"sourcedAnswer"` / `"structured"` | \$0.006 | | `"deep"` | `"searchResults"` | \$0.05 | | `"deep"` | `"sourcedAnswer"` / `"structured"` | \$0.055 | ## Fetch endpoint The [**Fetch**](/pages/documentation/endpoints/fetch/overview) endpoint is billed per call. Cost depends on `mode` and `renderJs`. Adding a `schema` for [structured output](/pages/documentation/endpoints/fetch/overview#structured-output) adds a flat \$0.001 on top of any combination. | `mode` | `renderJs` | Markdown only | With `schema` | | ------------ | ---------- | ---------------- | ---------------- | | `"standard"` | `false` | \$0.001 per call | \$0.002 per call | | `"standard"` | `true` | \$0.005 per call | \$0.006 per call | | `"pro"` | `false` | \$0.005 per call | \$0.006 per call | | `"pro"` | `true` | \$0.01 per call | \$0.011 per call | ## Research endpoint For the [**Research**](/pages/documentation/endpoints/research/overview) endpoint, the cost depends on the `reasoningDepth` parameter: | Call Type | Cost | | --------- | ------ | | `"S"` | \$0.25 | | `"M"` | \$0.50 | | `"L"` | \$1.50 | | `"XL"` | \$2.50 | ## Extract endpoint For the [**Extract**](/pages/documentation/endpoints/extract/overview) endpoint (closed beta), cost is variable and scales with crawl complexity. Most completed tasks fall in the **\$2–10** range. The exact amount charged is returned as `creditsUsed` in the task `output`. No credit is deducted for failed tasks. A minimum account balance of **\$10** is required to submit an extract task. ## Tasks endpoint Each task is billed exactly like the corresponding direct **Search**, **Fetch**, **Research**, or **Extract** call. ## x402 payment protocol You can also pay per request using the [x402 protocol](/pages/documentation/platform/x402) with USDC on Base — no account needed. x402 pricing follows the same per-request costs listed above. A minimum charge of **\$0.01 per request** applies. | Payment Method | Currency | Billing | | -------------- | ---------- | ------------------------------- | | API Key | USD | Prepaid credit balance | | x402 | USD (USDC) | Per-request on-chain settlement | **Important note**: No credit is subtracted when an error occurs, whether that is due to a missing parameter, an internal server error, or because we were unable to find anything relevant to your query. If your account runs out, the API will respond with a `429` HTTP error. You can view your current consumption in the [Billing](https://app.linkup.so/organization/billing) section of the Linkup app. # Rate Limits Source: https://docs.linkup.so/pages/documentation/platform/rate-limits For our [**Search**](/pages/documentation/endpoints/search/overview) and [**Fetch**](/pages/documentation/endpoints/fetch/overview) endpoints, our rate limits are currently set at `10 queries per second` per organization. For [x402](/pages/documentation/platform/x402) requests, rate limits are applied per IP address instead of per organization. If you need higher rate limits, please reach out to [contact@linkup.so](mailto:contact@linkup.so) to discuss Custom plans. # x402 Payment Protocol Source: https://docs.linkup.so/pages/documentation/platform/x402 Pay per request with USDC on Base. No account needed. Linkup supports the [x402](https://x402.org) payment protocol: pay per request in USDC on the Base network, with no account or API key required. This lets AI agents use Linkup autonomously, without requiring a human to create an account. If you already have an API key, nothing changes. x402 is an alternative way to access the API. ## Supported endpoints All x402-enabled endpoints are billed at a flat rate of **\$0.01 per request**. | Endpoint | | ------------ | | `/v1/search` | | `/v1/fetch` | ## How it works 1. **Send a request without authentication.** The API responds with `402` Payment Required and a `payment-required` header containing the amount, deposit address, network, and asset. 2. **Sign a USDC transfer** (EIP-712 / EIP-3009) with your wallet, then resend the same request with the signed payload in the `payment-signature` header. 3. **The API verifies the signature**, executes the payment on-chain, and returns `200` with a `payment-response` header containing the transaction hash. ## Setting up a wallet If you don't have a wallet yet, follow these steps to create one and fund it with USDC on Base. Download and install the [MetaMask](https://metamask.io/) app on your phone or browser extension. Follow the onboarding flow to create a new wallet. * Tap on your account name in the top left corner * Next to the account name, tap the **three dots** menu on the right * Select **Private key** * Copy your private key. This is what you'll use in the SDK to sign payments on **Base** * In MetaMask, switch to the **Base** network * Buy **USDC** (USD Coin). You may need to verify your identity and enter personal information * Complete the purchase Once your wallet is funded, you can use your private key in the SDK to pay for Linkup API requests. ## Quickstart ```bash theme={"system"} pip install 'linkup-sdk[x402]' ``` ```python theme={"system"} from eth_account import Account from linkup import LinkupClient from linkup.x402 import create_x402_signer account = Account.from_key("0xYOUR_PRIVATE_KEY") signer = create_x402_signer(account) client = LinkupClient(x402_signer=signer) result = client.search( query="What is Linkup?", depth="standard", output_type="sourcedAnswer", ) print(result.answer) ``` ```bash theme={"system"} npm install linkup-sdk @x402/core @x402/evm viem ``` ```js theme={"system"} import { LinkupClient } from 'linkup-sdk'; import { createX402Signer } from 'linkup-sdk/x402'; import { privateKeyToAccount } from 'viem/accounts'; const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY'); const signer = createX402Signer(account); const client = new LinkupClient({ signer }); const result = await client.search({ query: 'What is Linkup?', depth: 'standard', outputType: 'sourcedAnswer', }); console.log(result); ``` ## Headers reference | Header | Direction | Description | | ------------------- | ---------------- | ---------------------------------------------------- | | `payment-required` | Response (`402`) | Base64 JSON: amount, deposit address, network, asset | | `payment-signature` | Request | Base64 JSON: signed payment authorization | | `payment-response` | Response (`200`) | Base64 JSON: transaction hash, success status | For the full x402 specification, visit [x402.org](https://x402.org). The `@x402/core` and `@x402/evm` packages are open-source. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Automating Lead Qualification Source: https://docs.linkup.so/pages/documentation/tutorials/company-data-enrichment Learn how to automatically research and score your leads using Linkup AI to prioritize high-value prospects **Time-saving automation:** this tutorial shows you how to build a system that can process hundreds of leads automatically, saving 5+ hours of manual research per week while ensuring you focus on the right opportunities. # The challenge: from manual research to automated intelligence Every day, new users sign up for Linkup. When they do, we capture two crucial pieces of information: * Their company name * Their email address When we had a few signups every day, our team could spend time researching each company manually and identify the most important ones. But as we grow and the number of daily signups becomes too high to handle manually, we face a challenge: **How do we know which leads to focus on first?** **Before automation:** Our sales team spent hours manually researching each company, often missing high-value opportunities because of the volume. **After automation:** We instantly identify and prioritize the most promising leads based on AI-powered analysis of their company profile and website. ## The solution: an automated enrichment pipeline to qualify leads By the end of this tutorial, you'll have a working system that: 1. **Finds official websites:** cross-references company names with email domains 2. **Analyzes company websites:** determines how well each company fits your ideal customer profile 3. **Prioritizes leads:** assigns a score from 1-5 so your team knows who to contact first For this tutorial, we are going to use: * **Attio CRM data pull:** to generate the input .json file with org names and company domains. * **Linkup API:** to search the web and enrich the leads. ## The complete process: visual overview Here's how the data transforms throughout this process: ```mermaid theme={"system"} graph LR A[User Signup] -->|Extract| B[Name + Email Domain] B -->|Linkup API Lookup| C[Verified Website] C -->|Linkup API Analysis| D[ICP Score] style A fill:#f9d4d4 style D fill:#c9f7c9 ``` ## Project setup: getting started Let's start with our project structure and requirements: ```bash theme={"system"} your-project-directory/ ├── .env # API keys ├── organization_names_with_domains.json # Input file ├── company_analysis_results.json # Output file └── linkup_enrich.py # Enrichment script ``` First, let's set up our environment and install the required packages: ```bash theme={"system"} # Create a virtual environment python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate #### Install required packages pip install requests python-dotenv ``` Create a `.env` file in your project directory with your API keys: ```env theme={"system"} LINKUP_API_KEY=your_api_key_here ATTIO_API_KEY=your_attio_api_key_here ``` Create a Linkup account for free to get your API key. Never put your API KEYS directly in your code. Always include them in a `.env` file. Our starting point is a JSON file containing user signup information: * Organizations * Email address (or rather: the domains of the email addresses) In our case, we extracted this file from our CRM (Attio) since this is where we send signup data (and want to send the enriched data back after the process). This file could be the output of a signup form or any lead generation document you're using. Let's look at the structure: ```json theme={"system"} "034Q2K4NXVY6JIWZ": { "name": "test", "people_ids": "01HQ2K4NX123457", "domain": "anthropic.com" }, "01HQ2K4NXVY6GPWZ": { "name": "Personal", "people_ids": "01HQ2K4NX123456", "domain": "live.fr" }, "CHDY2K4NXVY6GJUE": { "name": "MistralAI", "people_ids": "01HQ2K4NX120987", "domain": "gmail.com" }, ``` As you can see, data quality might not be optimal: * Some users provide personal email addresses * Others do not put the name of their company This is why we're combining both information to try and get better results. We could add a Linkup search for LinkedIn profiles associated with the first part of the emails. ## The enrichment pipeline: step-by-step implementation Before we share the whole script (see the end of the tutorial), let's break down our enrichment pipeline into steps: Our first challenge is to reliably find the official website for each company. We'll use Linkup's API with a carefully crafted prompt: ```python {8-13} theme={"system"} url = "https://api.linkup.so/v1/search" headers = { "Authorization": f"Bearer {LINKUP_API_KEY}", "Content-Type": "application/json" } payload = { "q": f"Based on the name {company_name} and the email domain {domain_info}, " + f"find the most likely company website URL. Only return a result if you are 90% sure " + f"this is the correct website. If {company_name} seems like a generic company name " + f"(e.g. personal, perso, n/a), return nothing. If the domain is a generic domain " + f"(e.g. gmail.com, yahoo.com, hotmail.com, icloud.com), do not consider it. " + f"Only consider professional email domains. Only return the company domain URL.", "depth": "standard", "outputType": "sourcedAnswer", "includeImages": "false" } ``` **Understanding key parameters**: * **`depth: "standard"`**: for website finding, the agent breaks down your prompt into as many calls to our index as needed (both search and scrape) in a single pass. Use `"flash"` for a single, direct call to our index at the lowest latency. * **`outputType: "sourcedAnswer"`**: returns a natural-language answer with just the URL. * **`includeImages: false`**: we don't need images, which speeds up the response. Notice how the prompt includes specific instructions: * Only return results with 90% confidence * Ignore generic company names * Skip generic email domains * Focus on professional email domains These constraints help ensure we get high-quality, reliable results. Once we have the website, we need to determine how well each company fits our ideal customer profile. For this, we're using a second prompt to Linkup that gives the domain URL as input and asks for an ICP score as output. ```python {7-13} theme={"system"} url = "https://api.linkup.so/v1/search" headers = { "Authorization": f"Bearer {LINKUP_API_KEY}", "Content-Type": "application/json" } payload = { "q": f"Analyze the website {website_url}. Determine if this company could be an Ideal Customer Profile (ICP) " + f"for my company https://www.linkup.so/. For context, we are selling a search API. We target AI companies, " + f"Tech Companies, and corporates, as well as consulting and financial firms. " + f"Our search API allows companies to enrich applications with real-time web knowledge and business intelligence, " + f"at scale. Consider factors like industry and whether they're likely to need API services, and if they " + f"might be building software products. Return a rating from 1 to 5, 1 being lowest ICP, 5 being highest ICP. " + f"Universities and schools should get a 3. Only return the rating, nothing else.", "depth": "deep", "outputType": "sourcedAnswer", "includeImages": "false" } ``` **We use `"deep"` depth** for ICP analysis because: * It provides more thorough analysis of the company website * It considers more pages and context when making its assessment * The accuracy of ICP scoring is worth the slightly longer processing time **The output will be an ICP (Ideal Customer Profile) score ranging from 1-5:** * **1** - Perfect match - AI companies, Tech Companies with clear API needs * **2** - Strong potential - Corporates, Financial Firms, Consulting companies * **3** - Moderate fit - Universities, Educational Institutions, Research Organizations * **4** - Might need education - Companies with potential but unclear use cases * **5** - Probably not a good fit - Consumer businesses, local services, etc. Notice how we don't have to explicitly explain our rating system - AIs understand it intuitively. After we run the script with these two API calls, a new file will be created with two new fields for each company: * Website domain * ICP Analysis ```json {5-6,12-13,19-20} theme={"system"} "034Q2K4NXVY6JIWZ": { "name": "test", "people_ids": "01HQ2K4NX123457", "domain": "anthropic.com" "website": "https://www.anthropic.com/" "icp_analysis": "5" }, "01HQ2K4NXVY6GPWZ": { "name": "Personal", "people_ids": "01HQ2K4NX123456", "domain": "live.fr" "website": "" "icp_analysis": "1" }, "CHDY2K4NXVY6GJUE": { "name": "MistralAI", "people_ids": "01HQ2K4NX120987", "domain": "gmail.com" "website": "https://mistral.ai/" "icp_analysis": "5" } ``` Great! As you can see, combining company name and email domain allows us to identify ICPs we would have missed if we had only considered one of the two factors. The complete script below includes additional functionality beyond the two Linkup API calls shown above. For example: * We implement logic to skip the ICP analysis call when no website is found, automatically assigning a rating of "1" (as seen in the second example output) * We include an incremental processing system that only analyzes companies without existing ratings, preventing redundant API calls and allowing you to resume processing after interruptions * The code handles file operations safely, maintains a processing counter, and includes appropriate rate limiting between API calls ## Next steps and customization opportunities This script is just the beginning! Here are ways you can extend it: 1. **Additional enrichment**: add other API calls to find additional information (company industry, value chain positioning, pricing strategy...) 2. **CRM integration**: add code to push results back to your CRM automatically (what we're doing at Linkup) 3. **Multi-threaded processing**: speed up processing by handling multiple companies simultaneously In our case, we're actually sending the data back to our CRM so that new signups get automatically rated. We then have live alerts that tell us when important customers sign up to our products. ## The complete code Below is the complete Python script that implements our lead qualification system. The file is more complex than the two functions to allow for observability, troubleshooting, batch processing, etc. Do not hesitate to reach out if you have any questions. ```python [expandable] theme={"system"} import os import json import time import re from dotenv import load_dotenv import requests # Load environment variables load_dotenv() # API keys from environment variables ATTIO_API_KEY = os.getenv('ATTIO_API_KEY') LINKUP_API_KEY = os.getenv('LINKUP_API_KEY') if not ATTIO_API_KEY or not LINKUP_API_KEY: print("Error: Missing API keys in .env file") exit(1) # File paths and settings FILE = 'new_companies.json' MAX_COMPANIES = 50 def load_existing_data(): """Load file with new companies to enrich""" try: with open(FILE, 'r') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): print(f"Error: Could not load {FILE}") exit(1) def find_website_url(company_name, company_domain=None): if not company_name or not company_name.strip(): return "" url = "https://api.linkup.so/v1/search" headers = { "Authorization": f"Bearer {LINKUP_API_KEY}", "Content-Type": "application/json" } domain_info = company_domain if company_domain else "" payload = { "q": f"Based on the name {company_name} and the email domain {domain_info}, find the most likely company website URL. Only return a result if you are 90% sure this is the correct website. If {company_name} seems like a generic company name (e.g. personal, perso, n/a), return nothing. If the domain is a generic domain (e.g. gmail.com, yahoo.com, hotmail.com, icloud.com), do not consider it. Only consider professional email domains. Only return the company domain URL.", "depth": "standard", "outputType": "sourcedAnswer", "includeImages": "false" } print(f"Sending API request to Linkup for: {company_name}") try: response = requests.post(url, headers=headers, json=payload) print(f"API response status: {response.status_code}") response.raise_for_status() result = response.json() if 'answer' in result: url_text = result['answer'] url_pattern = re.compile(r'https?://\S+') url_match = url_pattern.search(url_text) if url_match: website_url = url_match.group(0) return re.sub(r'[.,;:"\')]\s*$', '', website_url) return "" except Exception as e: print(f"Error in API call: {e}") return "" def analyze_icp_fit(company_name, website_url): if not website_url: return "1" url = "https://api.linkup.so/v1/search" headers = { "Authorization": f"Bearer {LINKUP_API_KEY}", "Content-Type": "application/json" } payload = { "q": f"Analyze the website {website_url}. Determine if this company could be an Ideal Customer Profile (ICP) for my company https://www.linkup.so/. For context, we are selling a search API. We target AI companies, Tech Companies, and corporates, as well as consulting and financial firms. Our search API allows companies to enrich applications with real-time web knowledge and business intelligence, at scale. Consider factors like industry and whether they're likely to need API services, and if they might be building software products. Return a rating from 1 to 5, 1 being lowest ICP, 5 being highest ICP. Universities and schools should get a 3. Only return the rating, nothing else.", "depth": "deep", "outputType": "sourcedAnswer", "includeImages": "false" } print(f"Sending ICP analysis request to Linkup for: {company_name}") try: response = requests.post(url, headers=headers, json=payload) print(f"ICP API response status: {response.status_code}") response.raise_for_status() result = response.json() if 'answer' in result: return result['answer'] return "1" except Exception as e: print(f"Error in ICP API call: {e}") return "1" def save_results(results): print(f"\nSaving results to {FILE}...") try: # First try to save to a temporary file temp_file = f"{FILE}.tmp" with open(temp_file, 'w') as f: json.dump(results, f, indent=2) # If successful, rename the temp file to the actual file if os.path.exists(FILE): os.replace(temp_file, FILE) else: os.rename(temp_file, FILE) print(f"Successfully saved {len(results)} results") # Verify the save with open(FILE, 'r') as f: saved_data = json.load(f) print(f"Verified save: {len(saved_data)} results in file") except Exception as e: print(f"Error saving results: {e}") # Try to clean up temp file if it exists if os.path.exists(temp_file): try: os.remove(temp_file) except: pass def main(): results = load_existing_data() print(f"\nLoaded {len(results)} existing results") # Find organizations to process to_process = {record_id: (results[record_id]["name"], results[record_id].get("domain")) for record_id in results if not results[record_id].get("website") and results[record_id].get("name") and (not results[record_id].get("icp_analysis") or results[record_id].get("icp_analysis") == "")} companies_to_process = dict(list(to_process.items())[:MAX_COMPANIES]) print(f"\nFound {len(to_process)} organizations to process") print(f"Will process {len(companies_to_process)} organizations...") processed_count = 0 websites_found = 0 for record_id, (org_name, org_domain) in companies_to_process.items(): print(f"\nProcessing: {org_name}") company_result = results[record_id] website_url = find_website_url(org_name, org_domain) if website_url: company_result["website"] = website_url websites_found += 1 print(f"Found: {website_url}") time.sleep(2) icp_analysis = analyze_icp_fit(org_name, website_url) company_result["icp_analysis"] = icp_analysis print(f"Updated {org_name} with website: {website_url} and ICP: {icp_analysis}") else: # Set default ICP analysis to "1" when no website is found company_result["icp_analysis"] = "1" print(f"Updated {org_name} with default ICP: 1") results[record_id] = company_result processed_count += 1 # Save after each company save_results(results) print(f"Saved results after processing {org_name}") if processed_count < len(companies_to_process): time.sleep(3) websites_with_analysis = sum(1 for r in results.values() if r.get("website") and r.get("icp_analysis")) print(f"\nFinal Results:") print(f"Processed {processed_count} organizations") print(f"Found {websites_found} websites") print(f"Total organizations with websites: {sum(1 for r in results.values() if r.get('website'))}") print(f"Total organizations with analysis: {websites_with_analysis}") # Final save with verification save_results(results) if __name__ == "__main__": main() ``` # Try it yourself Try modifying the ICP analysis prompt to match your specific business needs: 1. Update the description of your company 2. List your target industries 3. Define what makes an ideal customer for you 4. Run the script and see how the results change Extend the script to also estimate company size: 1. Create a new function similar to `analyze_icp_fit` 2. Craft a prompt asking Linkup to estimate employee count 3. Add this data to your results structure 4. Use it as an additional factor in prioritization # Conclusion You've now built an automated system that transforms basic CRM information into actionable intelligence. By leveraging the Linkup API, you can: 1. **Save time**: eliminate manual research 2. **Improve targeting**: focus on the most promising leads 3. **Scale your process**: handle hundreds of leads with ease This approach combines the best of both worlds: AI-powered analysis with your business expertise to define what makes an ideal customer. For more sophisticated implementations, check out our other tutorials or reach out to our support team! **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Company Descriptions Generator Source: https://docs.linkup.so/pages/documentation/tutorials/company-descriptions Building a tool to generate rich company descriptions using Linkup API This tutorial will show you how to build a company description generator that takes a company name and country as input and returns comprehensive information about the company using the Linkup API's structured output feature. ## What we're building Our company description generator will: * Take a company name and country as input * Use Linkup API to search for information about the company * Return structured data about the company (description, industry, size, location, etc.) ## Building the generator Before we start coding, let's define the schema that specifies what information we want to extract about companies. This schema will be used throughout our implementation: ```python python [expandable] theme={"system"} COMPANY_SCHEMA = { "type": "object", "properties": { "name": { "type": "string", "description": "The official name of the company" }, "description": { "type": "string", "description": "A comprehensive description of what the company does" }, "location": { "type": "string", "description": "Location of company headquarters" }, "companySize": { "type": "string", "description": "Approximate number of employees" }, "linkedInUrl": { "type": "string", "description": "Company's LinkedIn profile URL" } }, "required": ["name", "description"] } ``` ```javascript js [expandable] theme={"system"} const COMPANY_SCHEMA = { "type": "object", "properties": { "name": { "type": "string", "description": "The official name of the company" }, "description": { "type": "string", "description": "A comprehensive description of what the company does" }, "location": { "type": "string", "description": "Location of company headquarters" }, "companySize": { "type": "string", "description": "Approximate number of employees" }, "linkedInUrl": { "type": "string", "description": "Company's LinkedIn profile URL" } }, "required": ["name", "description"] }; ``` This schema defines all the fields we want to extract about a company. The required fields are `name`, `description`, and `industry`, while the rest are optional but provide valuable additional information. Next, let's install the Linkup SDK in your preferred language: ```python python theme={"system"} pip install linkup-sdk ``` ```javascript js theme={"system"} npm i linkup-sdk ``` Initialize the Linkup client with your API key: Create a Linkup account for free to get your API key. ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '', }); ``` Create a function to generate the search query: ```python python theme={"system"} def generate_query(company_name: str, country: str) -> str: query = ( f"Find detailed information about {company_name} in {country}. " "Include their main products/services, industry focus, company size, " "and any notable achievements or recent news. Also find their " "LinkedIn company page if available." ) return query ``` ```typescript js theme={"system"} function generateQuery(companyName: string, country: string) { let query; query = ( `Find detailed information about ${companyName} in ${country}. ` + "Include their main products/services, industry focus, company size, " + "and any notable achievements or recent news. Also find their " + "LinkedIn company page if available." ); return query; } ``` Here's the complete implementation that puts everything together: ```python python theme={"system"} from linkup import LinkupClient import json from typing import Dict, Any from pprint import pprint # Schema definition (from Step 1) COMPANY_SCHEMA = { "type": "object", "properties": { "name": { "type": "string", "description": "The official name of the company" }, "description": { "type": "string", "description": "A comprehensive description of what the company does" }, "location": { "type": "string", "description": "Location of company headquarters" }, "companySize": { "type": "string", "description": "Approximate number of employees" }, "linkedInUrl": { "type": "string", "description": "Company's LinkedIn profile URL" } }, "required": ["name", "description"] } # Initialize the client client = LinkupClient(api_key="") # Query generator (from Step 4) def generate_query(company_name: str, country: str) -> str: query = ( f"Find detailed information about {company_name} in {country}. " "Include their main products/services, industry focus, company size, " "and any notable achievements or recent news. Also find their " "LinkedIn company page if available." ) return query def generate_company_description(company_name: str, country: str) -> Dict[str, Any]: """ Generate a structured description of a company using its name, country. Args: company_name: Name of the company country: Country where the company operates Returns: Dictionary containing structured company information """ try: # Clean input company_name = company_name.strip() country = country.strip() # Generate search query using the function from Step 4 query = generate_query(company_name, country) # Call Linkup API response = client.search( query=query, depth="deep", # Use deep for more thorough results output_type="structured", structured_output_schema=json.dumps(COMPANY_SCHEMA) ) return response except Exception as e: return { "error": str(e), "company_name": company_name, "country": country } # Example usage if __name__ == "__main__": # Example companies companies = [ ("Anthropic", "United States"), ("Stripe", "United States"), ("OpenAI", "United States") ] for company_name, country in companies: print(f"\nLooking up: {company_name} in {country}") result = generate_company_description(company_name, country) pprint(result) ``` ```typescript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; // Schema definition (from Step 1) const COMPANY_SCHEMA = { "type": "object", "properties": { "name": { "type": "string", "description": "The official name of the company" }, "description": { "type": "string", "description": "A comprehensive description of what the company does" }, "location": { "type": "string", "description": "Location of company headquarters" }, "companySize": { "type": "string", "description": "Approximate number of employees" }, "linkedInUrl": { "type": "string", "description": "Company's LinkedIn profile URL" } }, "required": ["name", "description"] }; // Initialize the client const client = new LinkupClient({ apiKey: '', }); // Query generator (from Step 4) function generateQuery(companyName: string, country: string) { let query; query = ( `Find detailed information about ${companyName} in ${country}. ` + "Include their main products/services, industry focus, company size, " + "and any notable achievements or recent news. Also find their " + "LinkedIn company page if available." ); return query; } async function generateCompanyDescription(companyName: string, country: string) { try { // Clean input companyName = companyName.trim(); country = country.trim(); // Generate search query using the function from Step 4 const query = generateQuery(companyName, country); // Call Linkup API const response = await client.search({ query: query, depth: "deep", // Use deep for more thorough results outputType: "structured", structuredOutputSchema: COMPANY_SCHEMA }); return response; } catch (error: any) { return { error: error.message, companyName: companyName, country: country }; } } // Example usage async function main() { // Example companies const companies = [ ["Anthropic", "United States"], ["Stripe", "United States"], ["OpenAI", "United States"] ]; for await (const [companyName, country] of companies) { console.log(`\nLooking up: ${companyName} in ${country}`); const result = await generateCompanyDescription(companyName, country); console.log(JSON.stringify(result, null, 2)); } } main().catch(console.error); ``` ## How it works The company description generator works in three main steps: 1. **Input processing**: the tool takes a company name and country as input and cleans them. 2. **Query generation**: it creates a search query that includes both the company name and country to improve search accuracy. 3. **Structured output**: uses Linkup's structured output feature with a comprehensive schema to ensure consistent, well-formatted results. ## Possible enhancements For a production version, consider adding: * Add as much information you have about companies in the queries, to limit ambiguity about which company you are searching for. * Implement rate limiting and error handling for API usage. * Batch processing to search for multiple companies in parallel if you are enriching a dataset for example. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Company Intelligence Engine Source: https://docs.linkup.so/pages/documentation/tutorials/company-intelligence/company-intelligence Build a real-time company research system using the Linkup API. In this tutorial, you'll learn how to build a real-time company intelligence system that automatically gathers and structures information about any company. This system is perfect for sales teams, market researchers, and anyone needing up-to-date company information. ## Prerequisites Before starting, ensure you have: * Python 3.7 or newer installed * Basic familiarity with Python programming * A Linkup API key (required for making requests) Create a Linkup account for free to get your API key. ## Project setup Let's start by creating a new project and installing the necessary dependencies: ```shell shell theme={"system"} # Create a new project directory mkdir company-intel cd company-intel # Install required packages pip install linkup-sdk pydantic fastapi uvicorn ``` ## Implementation guide ### 1. Define your data structure First, we'll create a data model that specifies exactly what company information we want to collect. Create a new file called `schema.py`: ```python python theme={"system"} from pydantic import BaseModel from typing import List, Optional class CompanyInfo(BaseModel): # Basic company information name: str = "" # Company name website: str = "" # Official website URL description: str = "" # Brief company description # Detailed information latest_funding: str = "" # Most recent funding information recent_news: List[str] = [] # Latest company news leadership_team: List[str] = [] # Key executives and leaders tech_stack: List[str] = [] # Technologies used by the company ``` This schema ensures our data is consistently structured and validated. ### 2. Build the intelligence engine Next, create `company_intel.py` to handle the core functionality: ```python python theme={"system"} from linkup import LinkupClient from schema import CompanyInfo class CompanyIntelligence: def __init__(self, api_key: str): """Initialize the intelligence engine with your API key.""" self.client = LinkupClient(api_key=api_key) def research_company(self, company_name: str) -> CompanyInfo: """ Gather comprehensive information about a company. Args: company_name (str): Name of the company to research Returns: CompanyInfo: Structured company information """ query = f""" Research {company_name} and provide: - Company name, website, and brief description - Most recent funding round or financial announcement - Current leadership team members - Technologies and tools they use - Recent news from the last 3 months Focus on current, verified information. """ # Make the API request with structured output response = self.client.search( query=query, depth="deep", # Get comprehensive results output_type="structured", structured_output_schema=CompanyInfo ) return response ``` ### 3. Create the API layer Now let's make our intelligence engine accessible via HTTP. Create `api.py`: ```python python theme={"system"} from fastapi import FastAPI, HTTPException from company_intel import CompanyIntelligence # Initialize FastAPI with metadata app = FastAPI( title="Company Intelligence API", description="Real-time company research and intelligence", version="1.0.0" ) # Create intelligence engine instance intel = CompanyIntelligence(api_key="your-linkup-api-key") @app.get("/company/{name}", tags=["Company Research"]) async def get_company_info(name: str): """ Retrieve detailed information about a company. Parameters: name (str): Company name to research Returns: CompanyInfo: Structured company information """ try: return intel.research_company(name) except Exception as e: raise HTTPException( status_code=500, detail=f"Error researching company: {str(e)}" ) ``` ## Using the system ### Basic usage example At first, let's use the intelligence engine directly in Python. We will create a new file called `main.py` and add the following code to it: ```python python theme={"system"} from company_intel import CompanyIntelligence # Initialize the intelligence engine intel = CompanyIntelligence(api_key="your-api-key") # Research a company company = intel.research_company("Vercel") # Access the information print(f"Company: {company.name}") print(f"Website: {company.website}") print(f"Description: {company.description}") print(f"Latest Funding: {company.latest_funding}") print(f"Recent News: {', '.join(company.recent_news)}") print(f"Leadership Team: {', '.join(company.leadership_team)}") print(f"Technologies: {', '.join(company.tech_stack)}") ``` Create a Linkup account for free to get your API key. You can run the code by executing the following command: ```shell shell theme={"system"} python main.py ``` ### Running the API server Let's now try our API server. ```shell shell theme={"system"} # Start the server with auto-reload enabled uvicorn api:app --reload ``` You can now access the API at `http://localhost:8000/docs`. #### Step 1 Click on the endpoint `GET /company/{name}`, and click on the `Try it out` button. Step 1 #### Step 2 Step 2 Now, enter the company name, for example `Vercel`, and click on the `Execute` button. #### Step 3 Step 3 #### Step 4 You should see the response in JSON format. Step 4 ## Response examples ### Successful response A typical successful request to `/company/Vercel` returns: ```json theme={"system"} { "name": "Vercel", "website": "https://vercel.com", "description": "Vercel is a cloud platform for static sites and Serverless Functions that fits perfectly with your workflow. It enables developers to host websites and web services that deploy instantly and scale automatically.", "latest_funding": "Series D - $150M (December 2023)", "recent_news": [ "Vercel announces Edge Functions improvements with streaming support", "New Next.js 14 release brings major performance improvements", "Vercel launches new Enterprise Security features" ], "leadership_team": [ "Guillermo Rauch - CEO", "Malte Ubl - CTO" ], "tech_stack": [ "Next.js", "React", "Node.js", "TypeScript", "PostgreSQL", "Redis", "AWS" ] } ``` ### Error responses The API returns appropriate error responses in these situations: #### Company not found ```json theme={"system"} { "status_code": 404, "detail": "Unable to find information for the specified company", "timestamp": "2024-12-16T10:30:45Z" } ``` #### Invalid API key ```json theme={"system"} { "status_code": 401, "detail": "Invalid or missing API key", "timestamp": "2024-12-16T10:30:45Z" } ``` #### Rate limit exceeded ```json theme={"system"} { "status_code": 429, "detail": "Rate limit exceeded. Please try again in 60 seconds", "timestamp": "2024-12-16T10:30:45Z" } ``` ## Engine response fields | Field | Type | Description | Example | | ---------------- | ------ | ------------------------------- | --------------------------------------------- | | name | string | Company name | "Vercel" | | website | string | Company website URL | "[https://vercel.com](https://vercel.com)" | | description | string | Brief company description | "Vercel is a cloud platform..." | | latest\_funding | string | Most recent funding information | "Series D - \$150M (December 2023)" | | recent\_news | array | List of recent news headlines | \["Vercel announces...", "New Next.js 14..."] | | leadership\_team | array | List of key leadership members | \["Guillermo Rauch - CEO", ...] | | tech\_stack | array | List of technologies used | \["Next.js", "React", ...] | | news\_sentiment | float | Optional sentiment score | 0.8 | | company\_size | string | Optional size estimation | "Large Enterprise" | ## Best practices For production use, consider implementing these best practices: 1. **Error handling** * Implement comprehensive error handling for API calls * Log errors appropriately * Provide meaningful error messages to users 2. **Rate limiting** * Implement rate limiting to avoid API quota issues * Consider using a queue for batch processing * Add retry logic for failed requests 3. **Caching** * Cache frequently requested company data * Use appropriate TTL values based on data freshness requirements * Implement cache invalidation strategies 4. **Data validation** * Validate all input data * Use Pydantic's validation features * Implement data cleaning where necessary ## Common applications This system can be used for: * Pre-sales research automation * CRM data enrichment * Competitor monitoring * Market intelligence gathering * Investment research * Due diligence automation ## Conclusion You now have a company intelligence engine that provides: * Real-time company information * Structured, consistent data * Easy API access * Extensible architecture Remember to: * Keep your API key secure * Respect API rate limits * Implement appropriate caching for your use case * Add error handling for production use **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Source Filtering Source: https://docs.linkup.so/pages/documentation/tutorials/filtering How to filter your search on sources. **Date filtering** information can be found [here](/pages/documentation/endpoints/search/reference#body-from-date). ## Overview You can fine-tune your search results by: * **including** * **excluding** * **prioritizing** specific domains or URLs, ensuring more control over where your answers are retrieved from. If you use `excludeDomains` and `includeDomains`, the results will logically only include URLs that match the inclusion. ### Exclude domains/URLs To filter your search results by exclusively excluding some URLs or domains, you can add the `excludeDomains` parameter to your API request. Currently, the parameter supports up to 50 URLs to exclude. **Example request with `excludeDomains`** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "Latest developments in AI", "depth": "standard", "outputType": "sourcedAnswer", "excludeDomains": ["apple.com"] }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """Latest developments in AI""" search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer", exclude_domains=["apple.com"] ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = `Latest developments in AI`; client.search({ query, depth: "standard", outputType: "sourcedAnswer", excludeDomains: ["apple.com"] }).then(console.log); ``` **Example response** ```json theme={"system"} { "answer": "AI agents are the dominant innovation in 2025, focusing on autonomous AI programs that can independently complete projects...", "sources": [ { "name": "AI Agents in 2025: Expectations vs. Reality | IBM", "snippet": "For 2025, the dominant innovation narrative is the AI agent...", "url": "https://www.ibm.com/think/insights/ai-agents-2025-expectations-vs-reality" } ] } ``` ### Include domains/URLs To filter your search results by restricting them to specific URLs or domains, you can add the `includeDomains` parameter to your API request. Currently, the parameter supports up to 50 URLs to include. **Example request with `includeDomains`** ```shell curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer {{LINKUP_API_KEY}}' \ --header 'Content-Type: application/json' \ --data '{ "q": "How to build MCP server", "depth": "standard", "outputType": "sourcedAnswer", "includeDomains": ["dev.to"] }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """How to build MCP server?""" search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer", include_domains=["dev.to"] ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = `How to build MCP server?`; client.search({ query, depth: "standard", outputType: "sourcedAnswer", includeDomains: ["dev.to"] }).then(console.log); ``` **Example response** ```json theme={"system"} { "answer": "Steps to build an MCP server in 2025:[...]", "sources": [ { "name": "🚀Top 10 MCP Servers for 2025 (Yes, GitHub’s Included!) - DEV Community", "snippet": "Enter the Model Context Protocol ... ", "url": "https://dev.to/fallon_jimmy/top-10-mcp-servers-for-2025-yes-githubs-included-15jg" } ] } ``` ### Prioritize domains To **prioritize** some domains, you can use the `` XML structure in your query. **Example request with prioritize** ```bash curl theme={"system"} curl -X POST 'https://api.linkup.so/v1/search' \ -H 'Authorization: Bearer {{LINKUP_API_KEY}}' \ -H 'Content-Type: application/json' \ -d '{ "q": "Use only the sources listed here. Do not rely on any external references.\n - wikipedia.org\n - lvmh.com\n\n - wikipedia.org\nWho is the CEO of LVMH?", "outputType": "sourcedAnswer", "depth": "standard" }' ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="{{LINKUP_API_KEY}}") query = """ Use only the sources listed here. Do not rely on any external references. - wikipedia.org - lvmh.com - wikipedia.org Who is the CEO of LVMH? """ search_response = client.search( query=query, depth="standard", output_type="sourcedAnswer" ) print(search_response) ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '{{LINKUP_API_KEY}}', }); const query = ` Use only the sources listed here. Do not rely on any external references. - wikipedia.org - lvmh.com - wikipedia.org Who is the CEO of LVMH?`; client.search({ query, depth: "standard", outputType: "sourcedAnswer" }).then(console.log); ``` **Example response** ```json theme={"system"} { "answer": "The CEO of LVMH is Bernard Arnault.", "sources": [ { "name": "Actual CEO of LVMH", "url": "https://www.wikipedia.org/wiki/lvmh", "snippet": "LVMH Moët Hennessy Louis Vuitton SE, commonly known as LVMH, is a French multinational luxury goods conglomerate. The current CEO is Bernard Arnault." }, { "name": "The CEO of LVMH is Bernard Arnault", "url": "https://lvmh.com/about-us", "snippet": "Bernard Arnault is the CEO of LVMH, a leading luxury goods company. Under his leadership, LVMH has seen significant growth and expansion in the luxury market." } ] } ``` # Find LinkedIn Profiles Source: https://docs.linkup.so/pages/documentation/tutorials/linkedin-profile A practical guide to quickly locate official LinkedIn home‑page URLs for companies or people ## What we'll build This tutorial will show you how to find the LinkedIn profile of a company or an individual using Linkup. * **Input**: Any text prompt that describes the profile you're after (company, school, person, etc.). * **Process**: 1. Send the prompt to the [**Search**](/pages/documentation/endpoints/search/overview) endpoint with `depth` set to `"standard"`. 2. Ask for `outputType` of `"sourcedAnswer"` so we can inspect citations. 3. Decide whether we're ≥ 99 % sure. * **Output**: A single LinkedIn URL or `undefined`. This pattern is perfect for enriching CRMs, onboarding forms, or internal tools where you need fast links with a very low false‑positive rate. ## How to build ```python python theme={"system"} pip install linkup-sdk ``` ```javascript js theme={"system"} npm i linkup-sdk ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '', }); ``` Create a Linkup account for free to get your API key. ```python python theme={"system"} # 🔎 Example response = client.search( query="Please locate the official LinkedIn page for Linkup (their website is linkup.so).\nReturn only the LinkedIn home‑page URL.\nIf you're not at least 99 % sure the link is accurate, answer with undefined.", depth="standard", # fast yet accurate output_type="sourcedAnswer", include_images=False, ) print(response.answer) # either the URL or 'undefined' ``` ```javascript js theme={"system"} // 🔎 Example const response = await client.search({ query: `Please locate the official LinkedIn page for Linkup (their website is linkup.so). Return only the LinkedIn home‑page URL. If you're not at least 99 % sure the link is accurate, answer with undefined.`, depth: 'standard', // fast yet accurate outputType: 'sourcedAnswer', includeImages: false, }); console.log(response.answer); // either the URL or 'undefined' ``` For best accuracy, include **at least two unique signals**: | Good signal | Example | | --------------------- | ---------------------------------- | | Official domain | "(their website is acme.com)" | | City or region | "based in Berlin, Germany" | | Stock ticker | "listed on NASDAQ : ACME" | | Unique slogan/tagline | "company slogan 'Think tangerine'" | > **Tip:** Keep the "Return only the LinkedIn home‑page URL…" and the 99 % clause; it gives the model explicit, measurable instructions. Need to process a list of companies? Here's a tiny batch helper: ```python python theme={"system"} companies = [ ("Stripe", "stripe.com"), ("Intercom", "intercom.com"), ("Monzo", "monzo.com"), ] for name, domain in companies: q = ( f"Locate the official LinkedIn page for {name} (their website is {domain}). " "Return only the LinkedIn home‑page URL. If you're not at least 99 % sure, answer with undefined." ) response = client.search( query=q, depth="standard", output_type="sourcedAnswer", include_images=False, ) print(name, "→", response.answer) ``` ```javascript js theme={"system"} const companies = [ ['Stripe', 'stripe.com'], ['Intercom', 'intercom.com'], ['Monzo', 'monzo.com'], ]; for (const [name, domain] of companies) { const q = `Locate the official LinkedIn page for ${name} (their website is ${domain}). ` + `Return only the LinkedIn home‑page URL. If you're not at least 99 % sure, answer with undefined.`; const response = await client.search({ query: q, depth: 'standard', outputType: 'sourcedAnswer', includeImages: false, }); console.log(name, '→', response.answer); } ``` ## Advanced enhancements * **Faster or deeper search**: try `"flash"` depth first for maximum speed on simple lookups, or fall back to `"deep"` when `"standard"` yields `undefined`. * **Multiple entities**: adjust the prompt to return an *array* of URLs when searching generic terms like "Acme Inc". * **Integrate with CRMs**: enrich company records automatically, then store URL + confidence + timestamp. * **Rate limiting**: use `asyncio` / `Promise.allSettled` with a limiter when batch‑processing thousands. ## Conclusion With fewer than 20 lines of code you now have a **LinkedIn URL resolver** that's fast, accurate, and configurable. Plug it into sign-up flows, prospecting pipelines, or internal dashboards, and never copy-paste a LinkedIn link again. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Meeting Prep Source: https://docs.linkup.so/pages/documentation/tutorials/meeting-prep/meeting-prep Build a meeting prep agent using Linkup inside Xpander ## Overview Linkup integrates with [Xpander.ai](http://xpander.ai/) to give your agents access to real-time web data. This tutorial walks through how to build a meeting preparation agent that uses that data to generate useful briefings. ## Configuration steps 1. Go to the Agents tab in Xpander. 2. Click “New Agent” 3. Name it something like “Meeting Brief Agent” Create Agent 4. Paste the following into the **Instructions** section: ``` Your job is to help someone prepare for a sales meeting. They’ll give you a company name. Use Linkup to find useful, recent info about that company. Write a clear, short briefing with: - What the company does - Recent updates - Their likely challenges - How we might approach them - Notes and key questions ``` Paste Prompt 1. Go to the Tools section in the agent builder Go Tools 2. Click “Add Tool” and select **Linkup.so** Add Tool 3. Choose the “Retrieve Web Content by Query” option Select Action 4. Under “Prompt Input Mapping”, link the user's input (e.g. company name) to the Linkup query **Query Tip:** ``` Give me recent updates, funding info, hiring trends, product launches, and blog posts about {{company}} ``` 1. Click the "+" button in the agent editor 2. Select **Send Email** tool 3. You don’t need to wire it to previous steps 4. Make sure your prompt includes: ``` Always email the final output to [email] ``` Send Email 1. Click the "+" button again 2. Select **Send Slack Message** Send Slack 3. Configure credentials with OAuth2 and log in to Slack Slack Auth 4. Include this in the prompt: ``` Also send the meeting briefing to the configured Slack channel ``` Company: Airtable Name: Daniel Kim Title: Director of Product Strategy Email: [daniel.kim@airtable.com](mailto:daniel.kim@airtable.com) Type into chat: > Can you prep a briefing for Daniel Kim from Airtable? Test Prompt Final Look You are all set to use Linkup to build an intelligent meeting prep agent in Xpander. Visit the [Concepts](/pages/documentation/endpoints/search/overview) page to learn more about how Linkup works. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Migrating from Exa Source: https://docs.linkup.so/pages/documentation/tutorials/migrating-from-exa Guide for switching from Exa Search, Contents, and Answer to Linkup Search and Fetch ## Overview Switch from Exa to Linkup's [**Search**](/pages/documentation/endpoints/search/overview), [**Fetch**](/pages/documentation/endpoints/fetch/overview), and [**Research**](/pages/documentation/endpoints/research/overview) endpoints. | Exa | Linkup | Role | | ---------------- | ----------------------------------------------------------------------- | --------------------------------- | | `POST /search` | `POST /v1/search` | Web search for AI agents | | `POST /contents` | `POST /v1/fetch` | Clean content from a known URL | | `POST /answer` | `POST /v1/search` with `outputType` `"sourcedAnswer"` or `"structured"` | Cited answer to a question | | Exa Agent | `POST /v1/research` | Long-running, multi-step research | Most of the migration is a mechanical rename. The part that is not mechanical is the query itself. Exa and Linkup both take a query and return web results, but they expect the query to be written differently. Porting a call across without rewriting the query text does not give you a representative result. ## The core difference Both APIs put intelligence into the request, in different places. | | Exa | Linkup | | ------------------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | What the query is | A description of the page you want to find, often a short noun phrase. | An instruction to a search agent: what to find, and what to bring back. | | Where you steer it | Through parameters: `category`, `contents`, `systemPrompt`, `additionalQueries`, date filters. | Through the query text. The parameter surface is deliberately small. | | What you get back | The best-matching **page**. | The best-matching **fact**, with its source. | Anything you expressed to Exa as a parameter needs to be expressed to Linkup as words in `q`. A bare noun phrase gives the Linkup agent very little to plan a retrieval around, so rewriting the query is usually the difference between a mediocre result and a good one. **Migrating many call sites?** Give a coding agent a representative sample of your existing Exa calls plus [linkup-for-agents](https://github.com/LinkupPlatform/linkup-for-agents), our context pack of knowledge files, workflow recipes, and skills, and have it produce the translated versions. Install the skills into your project: ```bash theme={"system"} npx skills add LinkupPlatform/skills ``` See also [Linkup for agents](/pages/documentation/get-started/for-agents). ## Quick start ### Get your API key Create a Linkup account for free to get your API key. ### Install the SDK ```bash Python theme={"system"} pip install linkup-sdk ``` ```bash JavaScript theme={"system"} npm install linkup-sdk ``` ## The three settings that matter most Everything else is optional. These three account for nearly all of the difference in output quality. ### 1. `depth`: choose the retrieval strategy Exa's `type` is primarily a speed setting. Linkup's `depth` selects *how* the search is carried out, so map it deliberately rather than as a direct rename. `"flash"` and `"fast"` pass the query as-is to the index, with no LLM. `"standard"` interprets the query and can run parallel sub-searches. `"deep"` adds the ability to open a page it discovered, read it, and decide what to do next. If step two depends on what step one found, or a discovered page has to be read, use `"deep"`. | Exa `type` | Linkup `depth` | Use when | | ------------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | | `"instant"` | `"flash"` | Lowest latency (\<200 ms). Ranked sources and snippets for chat, voice, autocomplete. | | `"fast"` | `"fast"` | One-shot retrieval where latency matters (\~1s). | | `"auto"` | `"standard"` | Everything you need can be searched for at once, in parallel (1–3s). | | `"deep-lite"`, `"deep"`, `"deep-reasoning"` | `"deep"` | Step two depends on step one, or a discovered page has to be opened and read (5–30s). | ### 2. `q`: write an instruction, not a description An Exa query describes a page. A Linkup query gives an instruction. Five moves that reliably improve results with `"standard"` and `"deep"`: 1. **Start with a verb.** Find, search, scrape, extract, compare. 2. **Name the fields you want back.** Linkup returns evidence rather than whole pages, so specify which evidence you need. 3. **Break breadth into explicit facets.** "Run separate searches for X, Y, and Z" is how you get parallel retrieval out of `"standard"`. This replaces Exa's `additionalQueries`. 4. **Repeat your constraints in every facet.** Without it, individual facets drift off-target. 5. **Ask for source URLs.** They make results verifiable at no extra cost. See [Search best practices](/pages/documentation/endpoints/search/best-practices) for more. ### 3. `outputType`: match it to what your code does next Exa returns pages and leaves the parsing to you. Linkup asks you to choose the response shape up front. | If your app… | Set `outputType` to | | ------------------------------------------- | -------------------------------------------------- | | Feeds results into your own LLM or reranker | `"searchResults"` | | Shows an answer to a person | `"sourcedAnswer"` | | Writes into a CRM, database, or spreadsheet | `"structured"` (requires `structuredOutputSchema`) | ## Migrate your search calls ### Replace your API calls **Exa** ```bash cURL theme={"system"} curl -X POST "https://api.exa.ai/search" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_EXA_KEY" \ -d '{ "query": "latest AI news", "type": "auto", "numResults": 10, "contents": { "highlights": true } }' ``` ```python Python theme={"system"} from exa_py import Exa exa = Exa(api_key="YOUR_EXA_KEY") response = exa.search( "latest AI news", type="auto", num_results=10, contents={"highlights": True}, ) print(response) ``` ```javascript JavaScript theme={"system"} import Exa from "exa-js"; const exa = new Exa("YOUR_EXA_KEY"); const response = await exa.search("latest AI news", { type: "auto", numResults: 10, contents: { highlights: true } }); console.log(response); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/search" \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ -H "Content-Type: application/json" \ -d '{ "q": "Find the latest AI news from the past week. Return the headline, publisher, and date for each story, with the source URL.", "depth": "standard", "outputType": "searchResults", "maxResults": 10 }' ``` ```python Python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_LINKUP_KEY") response = client.search( query="Find the latest AI news from the past week. Return the headline, publisher, and date for each story, with the source URL.", depth="standard", output_type="searchResults", max_results=10 ) print(response) ``` ```javascript JavaScript theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_LINKUP_KEY' }); client.search({ query: 'Find the latest AI news from the past week. Return the headline, publisher, and date for each story, with the source URL.', depth: 'standard', outputType: 'searchResults', maxResults: 10 }).then(console.log); ``` ### Parameter mapping | Exa parameter | Linkup parameter | Notes | | ---------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `query` | `q` | Required. **Rewrite it** as an instruction rather than copying it across. See the `q` section above | | `type` | `depth` | Required. Map `"instant"` → `"flash"`, `"fast"` → `"fast"`, `"auto"` → `"standard"`, `"deep-lite"` / `"deep"` / `"deep-reasoning"` → `"deep"` | | `numResults` | `maxResults` | Rename. `numResults` is not a Linkup parameter | | `category` | Express in `q` | `"company"` / `"people"`: "Find the official company page for…", or pass a LinkedIn URL directly. `"news"` / `"financial report"` / `"publication"`: "…from its most recent earnings release" | | `additionalQueries` | Express in `q` | "Run separate searches for: …". Works with `"standard"` and `"deep"` | | `includeDomains` | `includeDomains` | Array of domain strings, up to 100 entries (Exa allows 1,200). Reserve for domains you specifically need; a general preference such as "prefer official sources" works better in `q` | | `excludeDomains` | `excludeDomains` | Array of domain strings | | `startPublishedDate` | `fromDate` | ISO 8601 date (`YYYY-MM-DD`), not a full timestamp | | `endPublishedDate` | `toDate` | ISO 8601 date (`YYYY-MM-DD`), not a full timestamp | | `contents.highlights` | `outputType` `"searchResults"` | Linkup's default response shape. Each result includes a `content` snippet | | `contents.text` | **Fetch**, or `depth` `"deep"` | If you have the result URLs, call [**Fetch**](/pages/documentation/endpoints/fetch/overview) on them. To read pages the search discovers, use `"deep"` and say "scrape the page" in `q` | | `contents.summary` | `outputType` `"sourcedAnswer"` or `"structured"` | A summary schema maps to `structuredOutputSchema` | | `contents.subpages` | `depth` `"deep"` + instruction in `q` | For example: "if the page links to a pricing page, scrape that too" | | `contents.extras.imageLinks` | `includeImages` | Returns image results when supported | | `outputSchema` | `outputType` `"structured"` + `structuredOutputSchema` | JSON schema passed as a string; the root must be `type` `"object"`. See the [structured output guide](/pages/documentation/tutorials/structured-output-guide) | | `systemPrompt` | Express in `q` | "Prefer official sources" becomes a sentence in the query | | `userLocation` | Express in `q` | "…for the French market" | | `stream` | N/A | **Search** returns a single synchronous response | | `x-api-key` header | `Authorization: Bearer` header | — | ### Response format differences **Exa response structure** ```json theme={"system"} { "requestId": "b5947044c4b78efa9552a7c89b306d95", "results": [ { "title": "Page Title", "url": "https://example.com", "publishedDate": "2026-09-01T00:00:00.000Z", "highlights": ["Relevant excerpt..."] } ], "costDollars": { "total": 0.007 } } ``` **Linkup response structure (`"searchResults"`)** ```json theme={"system"} { "results": [ { "type": "text", "name": "Page Title", "url": "https://example.com", "content": "Relevant content..." } ] } ``` **Linkup response structure (`"sourcedAnswer"`)** ```json theme={"system"} { "answer": "Generated answer with citations...", "sources": [ { "name": "Page Title", "url": "https://example.com", "snippet": "Relevant excerpt..." } ] } ``` **Linkup response structure (`"structured"`)** ```json theme={"system"} { "customField1": "Extracted data following your schema...", "customField2": ["Array", "of", "values"] } ``` With `includeSources` set to `true`, the response becomes `{ "data": { ... }, "sources": [ ... ] }`. See the [structured output guide](/pages/documentation/tutorials/structured-output-guide). ### Worked example A typical Exa company search, translated. **Exa** ```json theme={"system"} { "query": "agtech companies in the US that have raised series A", "type": "auto", "category": "company", "numResults": 10, "contents": { "highlights": true } } ``` **Linkup** ```json theme={"system"} { "q": "Find US-based agtech companies that have raised a Series A round. Run separate searches for: US agtech startups that announced a Series A round; United States agriculture technology companies Series A funding round size and lead investor; American agtech Series A funding announcements in 2025 and 2026. For each company return the company name, headquarters, round size, announcement date, and lead investor, with the source URL for each finding.", "depth": "standard", "outputType": "searchResults", "maxResults": 10 } ``` What changed: * `category` `"company"` became words in the query. * `highlights` became `outputType` `"searchResults"`. * The noun phrase became an instruction with named fields. * One vague search became three parallel facets, with "US" and "Series A" repeated in all three so no facet drifts off-target. To write the results straight into a database, switch `outputType` to `"structured"` and pass a `structuredOutputSchema` describing one company per array item. ## Migrate your contents calls Exa **Contents** returns page content for known URLs. Linkup **Fetch** returns clean markdown for a single public URL (HTML or PDF), with optional JavaScript rendering and optional structured extraction. **Fetch** accepts one `url` per request. If you currently pass an array of URLs to Exa **Contents**, call Linkup **Fetch** once per URL (or in parallel). For batch workloads, submit your **Fetch** calls through [**Tasks**](/pages/documentation/endpoints/tasks/overview): up to 100 per submission, with the same parameters and pricing as direct `/fetch` calls. ### Replace your API calls **Exa** ```bash cURL theme={"system"} curl -X POST "https://api.exa.ai/contents" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_EXA_KEY" \ -d '{ "urls": ["https://docs.linkup.so"], "text": true }' ``` ```python Python theme={"system"} from exa_py import Exa exa = Exa(api_key="YOUR_EXA_KEY") response = exa.get_contents( ["https://docs.linkup.so"], text=True ) print(response) ``` ```javascript JavaScript theme={"system"} import Exa from "exa-js"; const exa = new Exa("YOUR_EXA_KEY"); const response = await exa.getContents(["https://docs.linkup.so"], { text: true }); console.log(response); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://docs.linkup.so", "renderJs": true }' ``` ```python Python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_LINKUP_KEY") response = client.fetch( url="https://docs.linkup.so", render_js=True, ) print(response) ``` ```javascript JavaScript theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_LINKUP_KEY' }); const response = await client.fetch({ url: 'https://docs.linkup.so', renderJs: true, }); console.log(response); ``` ### Parameter mapping | Exa parameter | Linkup parameter | Notes | | --------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `urls` / `ids` | `url` | One URL per request. Batch through [**Tasks**](/pages/documentation/endpoints/tasks/overview) | | `text` | N/A | `markdown` is always returned | | `summary` (with `schema`) | `schema` + `instructions` | Returns typed JSON in `data` alongside the markdown. Fields not found on the page are omitted, not invented | | `extras.imageLinks` | `extractImages` | Returns a separate list of image URLs | | `maxAgeHours` / `livecrawl` | N/A | **Fetch** retrieves the page in real time | | `subpages` | N/A | **Fetch** reads one URL and does not follow links. Use **Search** with `depth` `"deep"`, or [**Extract**](/pages/documentation/endpoints/extract/overview) (closed beta) for listing pages | | N/A | `renderJs` | Render client-side JavaScript before extraction | | N/A | `mode` | `"pro"` for hard-to-retrieve pages | ### Response format differences **Exa response structure** ```json theme={"system"} { "requestId": "b5947044c4b78efa9552a7c89b306d95", "results": [ { "id": "https://docs.linkup.so", "url": "https://docs.linkup.so", "title": "Page Title", "text": "Extracted content..." } ], "statuses": [{ "id": "https://docs.linkup.so", "status": "success" }] } ``` **Linkup response structure** ```json theme={"system"} { "markdown": "# Page title\n\nExtracted content...", "images": [ { "alt": "Logo", "url": "https://example.com/logo.svg" } ] } ``` `markdown` is always returned. `images` is empty unless `extractImages` is `true`. `data` is present only when `schema` is set. ### Example: structured extraction from a known URL Replaces Exa **Contents** with a `summary` schema. ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.linkup.so/pricing", "renderJs": true, "schema": { "type": "object", "properties": { "plans": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "price": { "type": "string", "description": "Public list price" } } } } } }, "instructions": "Express monetary values in USD." }' ``` ## Migrate your answer calls Exa **Answer** maps to Linkup **Search** with `outputType` `"sourcedAnswer"`. For a schema-shaped answer, use `outputType` `"structured"` with `structuredOutputSchema` in place of Exa's `outputSchema`. Fold `systemPrompt` and `userLocation` into `q`. **Exa** ```bash cURL theme={"system"} curl -X POST "https://api.exa.ai/answer" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_EXA_KEY" \ -d '{ "query": "What is the latest valuation of SpaceX?", "systemPrompt": "Prefer official sources." }' ``` ```python Python theme={"system"} response = exa.answer( "What is the latest valuation of SpaceX?", system_prompt="Prefer official sources." ) ``` ```javascript JavaScript theme={"system"} const response = await exa.answer("What is the latest valuation of SpaceX?", { systemPrompt: "Prefer official sources." }); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/search" \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ -H "Content-Type: application/json" \ -d '{ "q": "What is the latest valuation of SpaceX? Prefer official sources and recent reporting, and include the date of the valuation.", "depth": "standard", "outputType": "sourcedAnswer" }' ``` ```python Python theme={"system"} response = client.search( query="What is the latest valuation of SpaceX? Prefer official sources and recent reporting, and include the date of the valuation.", depth="standard", output_type="sourcedAnswer", ) print(response) ``` ```javascript JavaScript theme={"system"} const response = await client.search({ query: 'What is the latest valuation of SpaceX? Prefer official sources and recent reporting, and include the date of the valuation.', depth: 'standard', outputType: 'sourcedAnswer', }); console.log(response); ``` Exa returns `answer` plus `citations`. Linkup returns `answer` plus `sources`, each with `name`, `url`, and `snippet`. ## Migrate your agent calls For long-running research that Exa Agent handles (deep research, list building, enrichment), use Linkup [**Research**](/pages/documentation/endpoints/research/overview). It returns a sourced answer or structured output (`outputType` `"sourcedAnswer"` or `"structured"`), with `mode` and `reasoningDepth` to pin the type of investigation and its thoroughness. For list building and enrichment over many rows, also see [**Tasks**](/pages/documentation/endpoints/tasks/overview). ## Need help? * Check our [Quickstart Guide](/pages/documentation/get-started/quickstart) * Read [Search best practices](/pages/documentation/endpoints/search/best-practices) and [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices) * Join our [Discord Community](https://discord.gg/9q9mCYJa86) * Explore the [Search](/pages/documentation/endpoints/search/reference) and [Fetch](/pages/documentation/endpoints/fetch/reference) API references Your Linkup account starts with \$20 of free credit when you sign up with a professional email address. You can monitor usage and add more credit in the [Billing](https://app.linkup.so/organization/billing) section. # Migrating from SerpAPI Source: https://docs.linkup.so/pages/documentation/tutorials/migrating-from-serpapi Guide for switching from SerpAPI to Linkup ## Overview Linkup is powered by a proprietary, AI-native web index, which provides swift and relevant retrieval of web results. This is a fundamentally different approach than SerpAPI's, which scrapes Google's results page. ## Quick start ### Get your API key Create a Linkup account for free to get your API key. ### Install the SDK ```bash Python theme={"system"} pip install linkup-sdk ``` ```bash JavaScript theme={"system"} npm install linkup-sdk ``` ### Replace your API calls **SerpAPI** ```bash cURL theme={"system"} curl "https://serpapi.com/search?engine=google&q=latest%20AI%20news&num=10&api_key=YOUR_SERPAPI_KEY" ``` ```python Python theme={"system"} from serpapi import GoogleSearch params = { "engine": "google", "q": "latest AI news", "num": 10, "api_key": "YOUR_SERPAPI_KEY" } search = GoogleSearch(params) results = search.get_dict() ``` ```javascript JavaScript theme={"system"} const SerpApi = require('google-search-results-nodejs'); const search = new SerpApi.GoogleSearch("YOUR_SERPAPI_KEY"); const params = { engine: "google", q: "latest AI news", num: 10 }; search.json(params, (data) => { console.log(data); }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ --data-urlencode "q=latest AI news" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "maxResults=10" ``` ```python Python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_LINKUP_KEY") response = client.search( query="latest AI news", depth="standard", output_type="searchResults", max_results=10 ) print(response) ``` ```javascript JavaScript theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_LINKUP_KEY' }); client.search({ query: 'latest AI news', depth: 'standard', outputType: 'searchResults', maxResults: 10 }).then(console.log); ``` ## Parameter mapping | SerpAPI parameter | Linkup parameter | Notes | | ------------------ | ---------------------------------- | -------------------------------------------------------------- | | `q` | `query` | Required parameter | | `engine` | N/A | Linkup uses its own AI-native search engine | | `num` | `maxResults` | Total number of results returned. Default: `10` | | `tbs` (time-based) | `fromDate`, `toDate` | Use ISO 8601 date format (YYYY-MM-DD) | | `as_dt`, `as_eq` | `includeDomains`, `excludeDomains` | Use arrays of domain strings | | N/A | `depth` | Choose `"flash"`, `"fast"`, `"standard"`, or `"deep"` | | N/A | `outputType` | Choose `"searchResults"`, `"sourcedAnswer"`, or `"structured"` | | N/A | `structuredOutputSchema` | Define custom JSON schema for structured data extraction | ## Response format differences **SerpAPI response structure** ```json theme={"system"} { "organic_results": [ { "position": 1, "title": "Page Title", "link": "https://example.com", "snippet": "Description...", "date": "2 days ago" } ], "search_metadata": { "status": "Success", "total_time_taken": 1.23 } } ``` **Linkup response structure (`"searchResults"`)** ```json theme={"system"} { "results": [ { "name": "Page Title", "url": "https://example.com", "content": "Full content text...", "type": "html" } ] } ``` **Linkup response structure (`"sourcedAnswer"`)** ```json theme={"system"} { "answer": "Generated answer with citations...", "sources": [ { "name": "Page Title", "url": "https://example.com", "snippet": "Relevant excerpt..." } ] } ``` **Linkup response structure (`"structured"`)** ```json theme={"system"} { "output": { "customField1": "Extracted data following your schema...", "customField2": ["Array", "of", "values"] } } ``` ## Examples ### Fresh content search **SerpAPI** ```bash cURL theme={"system"} curl "https://serpapi.com/search?engine=google&q=AI+news&tbs=qdr:w&api_key=YOUR_KEY" ``` ```python Python theme={"system"} params = { "engine": "google", "q": "AI news", "tbs": "qdr:w", # Past week "api_key": "YOUR_KEY" } ``` ```javascript JavaScript theme={"system"} const SerpApi = require('google-search-results-nodejs'); const search = new SerpApi.GoogleSearch("YOUR_KEY"); const params = { engine: "google", q: "AI news", tbs: "qdr:w" }; search.json(params, (data) => { console.log(data); }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=AI news" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=sourcedAnswer" \ --data-urlencode "fromDate=2026-03-05" ``` ```python Python theme={"system"} response = client.search( query="AI news", depth="standard", output_type="sourcedAnswer", from_date="2026-03-05" ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'AI news', depth: 'standard', outputType: 'sourcedAnswer', fromDate: '2026-03-05' }).then(console.log); ``` ### Domain-specific search **SerpAPI** ```bash cURL theme={"system"} curl "https://serpapi.com/search?engine=google&q=transformers&as_dt=arxiv.org&api_key=YOUR_KEY" ``` ```python Python theme={"system"} params = { "engine": "google", "q": "transformers", "as_dt": "arxiv.org", # Domain to search "api_key": "YOUR_KEY" } ``` ```javascript JavaScript theme={"system"} const SerpApi = require('google-search-results-nodejs'); const search = new SerpApi.GoogleSearch("YOUR_KEY"); const params = { engine: "google", q: "transformers", as_dt: "arxiv.org" }; search.json(params, (data) => { console.log(data); }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=transformers" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "includeDomains=arxiv.org" ``` ```python Python theme={"system"} response = client.search( query="transformers", depth="standard", output_type="searchResults", include_domains=["arxiv.org"] ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'transformers', depth: 'standard', outputType: 'searchResults', includeDomains: ['arxiv.org'] }).then(console.log); ``` ### Excluding domains **SerpAPI** ```bash cURL theme={"system"} curl "https://serpapi.com/search?engine=google&q=python+tutorial&as_eq=w3schools.com&api_key=YOUR_KEY" ``` ```python Python theme={"system"} params = { "engine": "google", "q": "python tutorial", "as_eq": "w3schools.com", # Domain to exclude "api_key": "YOUR_KEY" } ``` ```javascript JavaScript theme={"system"} const SerpApi = require('google-search-results-nodejs'); const search = new SerpApi.GoogleSearch("YOUR_KEY"); const params = { engine: "google", q: "python tutorial", as_eq: "w3schools.com" }; search.json(params, (data) => { console.log(data); }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=python tutorial" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "excludeDomains=w3schools.com" ``` ```python Python theme={"system"} response = client.search( query="python tutorial", depth="standard", output_type="searchResults", exclude_domains=["w3schools.com"] ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'python tutorial', depth: 'standard', outputType: 'searchResults', excludeDomains: ['w3schools.com'] }).then(console.log); ``` ## Need help? * Check our [Quickstart Guide](/pages/documentation/get-started/quickstart) * Read about [Prompting Best Practices](/pages/documentation/endpoints/search/best-practices) * Join our [Discord Community](https://discord.gg/9q9mCYJa86) * Explore the [API Reference](/pages/documentation/endpoints/search/reference) Your Linkup account starts with \$20 of free credit when you sign up with a professional email address. You can monitor usage and add more credit in the [Billing](https://app.linkup.so/organization/billing) section. # Migrating from Tavily Source: https://docs.linkup.so/pages/documentation/tutorials/migrating-from-tavily Guide for switching from Tavily Search and Extract to Linkup Search and Fetch ## Overview Switch from Tavily's **Search** and **Extract** to Linkup's [**Search**](/pages/documentation/endpoints/search/overview) and [**Fetch**](/pages/documentation/endpoints/fetch/overview) in a line of code. | Tavily | Linkup | Role | | --------------- | ----------------- | -------------------------------- | | `POST /search` | `POST /v1/search` | Web search for AI agents | | `POST /extract` | `POST /v1/fetch` | Extract clean content from a URL | **Search** is Linkup's synchronous web search endpoint, optimized for AI consumption. Given a natural-language query, the `/search` route returns ranked sources, optionally bundled with a cited natural-language answer or with structured output matching a JSON schema. **Fetch** is Linkup's real-time page content extractor. Given a URL, the `/fetch` route returns a clean markdown rendering of the page, optimized for LLM consumption. ## Quick start ### Get your API key Create a Linkup account for free to get your API key. ### Install the SDK ```bash Python theme={"system"} pip install linkup-sdk ``` ```bash JavaScript theme={"system"} npm install linkup-sdk ``` ## Migrate your search calls ### Replace your API calls **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/search" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "query": "latest AI news", "search_depth": "basic", "max_results": 10 }' ``` ```python Python theme={"system"} from tavily import TavilyClient client = TavilyClient(api_key="tvly-YOUR_API_KEY") response = client.search( query="latest AI news", search_depth="basic", max_results=10 ) print(response) ``` ```javascript JavaScript theme={"system"} const { tavily } = require("@tavily/core"); const client = tavily({ apiKey: "tvly-YOUR_API_KEY" }); const response = await client.search("latest AI news", { searchDepth: "basic", maxResults: 10 }); console.log(response); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ --data-urlencode "q=latest AI news" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "maxResults=10" ``` ```python Python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_LINKUP_KEY") response = client.search( query="latest AI news", depth="standard", output_type="searchResults", max_results=10 ) print(response) ``` ```javascript JavaScript theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_LINKUP_KEY' }); client.search({ query: 'latest AI news', depth: 'standard', outputType: 'searchResults', maxResults: 10 }).then(console.log); ``` ### Parameter mapping | Tavily parameter | Linkup parameter | Notes | | --------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `query` | `query` | Required parameter | | `max_results` | `maxResults` | Total number of results returned. Linkup default: `10` | | `search_depth` | `depth` | Map `"ultra-fast"` → `"flash"`, `"fast"` → `"fast"`, `"basic"` → `"standard"`, `"advanced"` → `"deep"` | | `include_answer` | `outputType` | Use `"sourcedAnswer"` instead of `true` / `"basic"` / `"advanced"` | | `include_raw_content` | N/A (or **Fetch**) | Linkup `"searchResults"` already returns content snippets. For full page content, call [**Fetch**](/pages/documentation/endpoints/fetch/overview) on the result URLs | | `include_domains` | `includeDomains` | Array of domain strings | | `exclude_domains` | `excludeDomains` | Array of domain strings | | `start_date` | `fromDate` | ISO 8601 date (`YYYY-MM-DD`) | | `end_date` | `toDate` | ISO 8601 date (`YYYY-MM-DD`) | | `time_range` | `fromDate` | Approximate with an explicit date (e.g. `"week"` → seven days ago) | | `include_images` | `includeImages` | Returns image results when supported | | N/A | `outputType` | Choose `"searchResults"`, `"sourcedAnswer"`, or `"structured"` | | N/A | `structuredOutputSchema` | Define a custom JSON schema when `outputType` is `"structured"` | ### Response format differences **Tavily response structure** ```json theme={"system"} { "query": "latest AI news", "results": [ { "title": "Page Title", "url": "https://example.com", "content": "Snippet or summary...", "score": 0.95 } ], "answer": "Optional LLM answer when include_answer is set..." } ``` **Linkup response structure (`"searchResults"`)** ```json theme={"system"} { "results": [ { "name": "Page Title", "url": "https://example.com", "content": "Full content text...", "type": "html" } ] } ``` **Linkup response structure (`"sourcedAnswer"`)** — replaces Tavily `include_answer` ```json theme={"system"} { "answer": "Generated answer with citations...", "sources": [ { "name": "Page Title", "url": "https://example.com", "snippet": "Relevant excerpt..." } ] } ``` **Linkup response structure (`"structured"`)** ```json theme={"system"} { "output": { "customField1": "Extracted data following your schema...", "customField2": ["Array", "of", "values"] } } ``` ### Examples #### Fresh content search **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/search" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "query": "AI news", "time_range": "week", "include_answer": true }' ``` ```python Python theme={"system"} response = client.search( query="AI news", time_range="week", include_answer=True ) ``` ```javascript JavaScript theme={"system"} const response = await client.search("AI news", { timeRange: "week", includeAnswer: true }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=AI news" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=sourcedAnswer" \ --data-urlencode "fromDate=2026-03-05" ``` ```python Python theme={"system"} from datetime import date response = client.search( query="AI news", depth="standard", output_type="sourcedAnswer", from_date=date(2026, 3, 5) ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'AI news', depth: 'standard', outputType: 'sourcedAnswer', fromDate: new Date('2026-03-05') }).then(console.log); ``` #### Domain-specific search **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/search" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "query": "transformers", "include_domains": ["arxiv.org"] }' ``` ```python Python theme={"system"} response = client.search( query="transformers", include_domains=["arxiv.org"] ) ``` ```javascript JavaScript theme={"system"} const response = await client.search("transformers", { includeDomains: ["arxiv.org"] }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=transformers" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "includeDomains=arxiv.org" ``` ```python Python theme={"system"} response = client.search( query="transformers", depth="standard", output_type="searchResults", include_domains=["arxiv.org"] ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'transformers', depth: 'standard', outputType: 'searchResults', includeDomains: ['arxiv.org'] }).then(console.log); ``` #### Excluding domains **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/search" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "query": "python tutorial", "exclude_domains": ["w3schools.com"] }' ``` ```python Python theme={"system"} response = client.search( query="python tutorial", exclude_domains=["w3schools.com"] ) ``` ```javascript JavaScript theme={"system"} const response = await client.search("python tutorial", { excludeDomains: ["w3schools.com"] }); ``` **Linkup** ```bash cURL theme={"system"} curl "https://api.linkup.so/v1/search" \ -G \ -H "Authorization: Bearer YOUR_KEY" \ --data-urlencode "q=python tutorial" \ --data-urlencode "depth=standard" \ --data-urlencode "outputType=searchResults" \ --data-urlencode "excludeDomains=w3schools.com" ``` ```python Python theme={"system"} response = client.search( query="python tutorial", depth="standard", output_type="searchResults", exclude_domains=["w3schools.com"] ) print(response) ``` ```javascript JavaScript theme={"system"} client.search({ query: 'python tutorial', depth: 'standard', outputType: 'searchResults', excludeDomains: ['w3schools.com'] }).then(console.log); ``` ## Migrate your extraction calls Tavily **Extract** pulls content from one or more URLs. Linkup **Fetch** extracts clean markdown from a single public URL (HTML or PDF), with optional JavaScript rendering. **Fetch** accepts one `url` per request. If you currently pass an array of URLs to Tavily **Extract**, call Linkup **Fetch** once per URL (or in parallel). For batch workloads, submit your **Fetch** calls through [**Tasks**](/pages/documentation/endpoints/tasks/overview) — up to 100 per submission, with exactly the same parameters and pricing as direct `/fetch` calls. ### Replace your API calls **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/extract" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "urls": ["https://docs.linkup.so"] }' ``` ```python Python theme={"system"} from tavily import TavilyClient client = TavilyClient(api_key="tvly-YOUR_API_KEY") response = client.extract( urls=["https://docs.linkup.so"] ) print(response) ``` ```javascript JavaScript theme={"system"} const { tavily } = require("@tavily/core"); const client = tavily({ apiKey: "tvly-YOUR_API_KEY" }); const response = await client.extract(["https://docs.linkup.so"]); console.log(response); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer YOUR_LINKUP_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://docs.linkup.so", "renderJs": true }' ``` ```python Python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_LINKUP_KEY") response = client.fetch( url="https://docs.linkup.so", render_js=True, ) print(response) ``` ```javascript JavaScript theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_LINKUP_KEY' }); const response = await client.fetch({ url: 'https://docs.linkup.so', renderJs: true, }); console.log(response); ``` ### Response format differences **Tavily response structure** ```json theme={"system"} { "results": [ { "url": "https://docs.linkup.so", "raw_content": "# Page title\n\nExtracted content..." } ], "failed_results": [] } ``` **Linkup response structure** ```json theme={"system"} { "markdown": "# Page title\n\nExtracted content...", "rawHtml": "...", "images": [ { "alt": "Logo", "url": "https://example.com/logo.svg" } ] } ``` `rawHtml` is present only when `includeRawHtml` is `true`. `images` is present only when `extractImages` is `true`. `markdown` is always returned. ### Examples #### JavaScript-rendered pages **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/extract" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "urls": ["https://example.com/dashboard"], "extract_depth": "advanced" }' ``` ```python Python theme={"system"} response = client.extract( urls=["https://example.com/dashboard"], extract_depth="advanced" ) ``` ```javascript JavaScript theme={"system"} const response = await client.extract(["https://example.com/dashboard"], { extractDepth: "advanced" }); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/dashboard", "renderJs": true }' ``` ```python Python theme={"system"} response = client.fetch( url="https://example.com/dashboard", render_js=True, ) print(response) ``` ```javascript JavaScript theme={"system"} const response = await client.fetch({ url: 'https://example.com/dashboard', renderJs: true, }); console.log(response); ``` #### Extract images with content **Tavily** ```bash cURL theme={"system"} curl -X POST "https://api.tavily.com/extract" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tvly-YOUR_API_KEY" \ -d '{ "urls": ["https://docs.linkup.so"], "include_images": true }' ``` ```python Python theme={"system"} response = client.extract( urls=["https://docs.linkup.so"], include_images=True ) ``` ```javascript JavaScript theme={"system"} const response = await client.extract(["https://docs.linkup.so"], { includeImages: true }); ``` **Linkup** ```bash cURL theme={"system"} curl -X POST "https://api.linkup.so/v1/fetch" \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://docs.linkup.so", "extractImages": true }' ``` ```python Python theme={"system"} response = client.fetch( url="https://docs.linkup.so", extract_images=True ) print(response) ``` ```javascript JavaScript theme={"system"} const response = await client.fetch({ url: 'https://docs.linkup.so', extractImages: true }); console.log(response); ``` ## Need help? * Check our [Quickstart Guide](/pages/documentation/get-started/quickstart) * Read [Search best practices](/pages/documentation/endpoints/search/best-practices) and [Fetch best practices](/pages/documentation/endpoints/fetch/best-practices) * Join our [Discord Community](https://discord.gg/9q9mCYJa86) * Explore the [Search](/pages/documentation/endpoints/search/reference) and [Fetch](/pages/documentation/endpoints/fetch/reference) API references Your Linkup account starts with \$20 of free credit when you sign up with a professional email address. You can monitor usage and add more credit in the [Billing](https://app.linkup.so/organization/billing) section. # Peers Finder Source: https://docs.linkup.so/pages/documentation/tutorials/peers-finder/peers-finder Learn how to find comparable companies using the Linkup API with Google Sheets This guide shows how to combine the Linkup API with Google Sheets to build an automated peer comparison tool. Useful for investors, market analysts, and business strategists who need accurate competitive intelligence without standing up a custom backend. Don't walk away if you're not a developer! This tutorial is designed to be accessible for everyone - no coding experience required. Just follow our simple copy-paste instructions and clear screenshots to set up your own peer comparison tool in minutes. ## Prerequisites Before starting, ensure you have: * A Google account * A Linkup API key (required for making requests) Create a Linkup account for free to get your API key. ## Setup guide ### Step 1: open Google Sheets 1. Open a new Google Sheet 2. Name it "Company Peer Finder" or any name you prefer ### Step 2: access Apps Script 1. Click on "Extensions" in the menu bar 2. Select "Apps Script" from the dropdown menu Extensions Menu *1: Accessing Apps Script through the Extensions menu* ### Step 3: set up the script 1. In the Apps Script editor, delete any existing code 2. Copy and paste the following script: ```javascript theme={"system"} // Google Apps Script for Company Peer Finder with direct Linkup integration // This script uses Linkup API to find peer companies based on input companies // Configuration - replace with your actual Linkup API key const LINKUP_API_KEY = "YOUR_API_KEY"; // Replace with your Linkup API key const LINKUP_API_URL = "https://api.linkup.so/v1"; // Linkup API base URL // Create the menu when the spreadsheet opens function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('Peer Finder') .addItem('Extract Common Characteristics', 'extractCommonCharacteristics') .addItem('Find Peer Companies', 'findPeerCompanies') .addToUi(); } // Function to extract common characteristics from input companies function extractCommonCharacteristics() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const inputSheet = ss.getSheetByName('Input Companies'); const charSheet = ss.getSheetByName('Characteristics'); if (!inputSheet || !charSheet) { setupSpreadsheet(); return; } const companies = inputSheet.getRange('A2:A6').getValues().flat().filter(Boolean); if (companies.length === 0) { SpreadsheetApp.getUi().alert('Please enter at least one company name in the Input Companies sheet.'); return; } const characteristics = []; for (const company of companies) { const response = fetchCompanyData(company); if (response && response.characteristics) { characteristics.push(...response.characteristics); } } // Count frequency of each characteristic const charCount = {}; characteristics.forEach(char => { charCount[char] = (charCount[char] || 0) + 1; }); // Sort by frequency const sortedChars = Object.entries(charCount) .sort((a, b) => b[1] - a[1]) .map(([char, count]) => [char, count, false]); // Update characteristics sheet charSheet.clear(); charSheet.getRange(1, 1, 1, 3).setValues([['Characteristic', 'Frequency', 'Use for Search']]); if (sortedChars.length > 0) { charSheet.getRange(2, 1, sortedChars.length, 3).setValues(sortedChars); } } // Function to find peer companies based on selected characteristics function findPeerCompanies() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const charSheet = ss.getSheetByName('Characteristics'); const resultsSheet = ss.getSheetByName('Peer Companies'); if (!charSheet || !resultsSheet) { setupSpreadsheet(); return; } const selectedChars = charSheet.getRange('B2:B').getValues() .flat() .map((checked, i) => checked ? charSheet.getRange(i + 2, 1).getValue() : null) .filter(Boolean); if (selectedChars.length === 0) { SpreadsheetApp.getUi().alert('Please select at least one characteristic to use for finding peer companies.'); return; } const peers = findPeersByCharacteristics(selectedChars); // Update results sheet resultsSheet.clear(); resultsSheet.getRange(1, 1, 1, 4).setValues([['Company', 'Match Score', 'Industry', 'Size']]); if (peers.length > 0) { resultsSheet.getRange(2, 1, peers.length, 4).setValues(peers); } } // Helper function to fetch company data from Linkup API function fetchCompanyData(companyName) { const options = { method: 'GET', headers: { 'Authorization': `Bearer ${LINKUP_API_KEY}`, 'Content-Type': 'application/json' } }; try { const response = UrlFetchApp.fetch(`${LINKUP_API_URL}/companies/search?q=${encodeURIComponent(companyName)}`, options); return JSON.parse(response.getContentText()); } catch (error) { console.error(`Error fetching data for ${companyName}:`, error); return null; } } // Helper function to find peers based on characteristics function findPeersByCharacteristics(characteristics) { const options = { method: 'POST', headers: { 'Authorization': `Bearer ${LINKUP_API_KEY}`, 'Content-Type': 'application/json' }, payload: JSON.stringify({ characteristics }) }; try { const response = UrlFetchApp.fetch(`${LINKUP_API_URL}/companies/peers`, options); return JSON.parse(response.getContentText()); } catch (error) { console.error('Error finding peers:', error); return []; } } // Function to set up the initial spreadsheet structure function setupSpreadsheet() { const ss = SpreadsheetApp.getActiveSpreadsheet(); // Create or clear sheets const sheets = ['Input Companies', 'Characteristics', 'Peer Companies']; sheets.forEach(sheetName => { let sheet = ss.getSheetByName(sheetName); if (!sheet) { sheet = ss.insertSheet(sheetName); } sheet.clear(); }); // Set up Input Companies sheet const inputSheet = ss.getSheetByName('Input Companies'); inputSheet.getRange('A1').setValue('Enter up to 5 company names below:'); inputSheet.getRange('A2:A6').setBackground('#f3f3f3'); // Set up Characteristics sheet const charSheet = ss.getSheetByName('Characteristics'); charSheet.getRange('A1:C1').setValues([['Characteristic', 'Frequency', 'Use for Search']]); charSheet.getRange('C2:C').setDataValidation(SpreadsheetApp.newDataValidation() .requireCheckbox() .build()); // Set up Peer Companies sheet const resultsSheet = ss.getSheetByName('Peer Companies'); resultsSheet.getRange('A1:D1').setValues([['Company', 'Match Score', 'Industry', 'Size']]); // Hide unused sheets ss.getSheets().forEach(sheet => { if (!sheets.includes(sheet.getName())) { sheet.hideSheet(); } }); SpreadsheetApp.getUi().alert('Spreadsheet setup complete! You can now enter company names and use the Peer Finder menu.'); } ``` ### Step 4: configure your API key 1. Replace `YOUR_API_KEY` with your actual Linkup API key 2. If you don't have an API key: * Go to [app.linkup.so](https://app.linkup.so) * Create an account or sign in * Navigate to your API settings * Copy your API key ### Step 5: save the script 1. Click the "Save" button (or press Ctrl+S/Cmd+S) ### Step 6: authorize the script 1. When prompted, authorize the script to: * Access your Google Sheets * Make external API calls * Display dialogs Authorization Screen *2: The Google authorization screen* ### Step 7: initialize the spreadsheet 1. Click "Run" and select "SetupSpreadsheet" Setup Spreadsheet *3: Selecting SetupSpreadsheet* ### Step 8: verify setup 1. Look for a confirmation popup in your main sheet Confirmation Popup *4: The confirmation popup* ### Step 9: launch the tool 1. Click "Run" and select "OnOpen" OnOpen Selection *5: Selecting OnOpen* ## Using the peer finder ### Step 1: enter companies 1. Go to the "Input Companies" sheet 2. Enter up to 5 companies in cells A2-A6 3. Make sure to enter the full company names ### Step 2: extract characteristics 1. Click on the "Peer Finder" menu 2. Select "Extract Common Characteristics" 3. Wait for the process to complete 4. Review the extracted characteristics in the "Characteristics" sheet ### Step 3: select characteristics 1. In the "Characteristics" sheet, review the list of common characteristics 2. Use the checkboxes in column B to select which characteristics to use for finding peer companies 3. Select at least one characteristic ### Step 4: find peer companies 1. Click on the "Peer Finder" menu again 2. Select "Find Peer Companies" 3. Wait for the process to complete 4. Review the list of peer companies in the "Peer Companies" sheet ### Step 5: review and refine 1. Review the list of peer companies 2. If needed, go back to Step 3 and adjust your characteristic selections 3. Run the peer finder again with different characteristics For more detailed examples and use cases, check out our [prompting best practices](/pages/documentation/endpoints/search/best-practices). **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Signup Radar Source: https://docs.linkup.so/pages/documentation/tutorials/signup-radar Building a signup radar to get to know your new users This tutorial will show you how to build a simple "signup radar" that takes an email address and returns information about the person who signed up, using the Linkup API's structured output feature. ## What we're building Our signup radar will: * Take an email address as input * Use Linkup API to search for information about the person * Return structured data about the person (name, position, company, LinkedIn URL, etc.) ## Prerequisites * A Linkup API key * Python or Node.js installed Create a Linkup account for free to get your API key. ```python python theme={"system"} pip install linkup-sdk ``` ```javascript js theme={"system"} npm i linkup-sdk ``` ```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="") ``` ```javascript js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: '', }); ``` The key to our signup radar is using Linkup's structured output feature. We need to define a schema that specifies what information we want to extract. ```python python theme={"system"} import json schema = { "type": "object", "properties": { "fullName": { "type": "string", "description": "The full name of the person" }, "company": { "type": "string", "description": "The company the person works for" }, "position": { "type": "string", "description": "The job title or position of the person" }, "linkedInUrl": { "type": "string", "description": "The LinkedIn profile URL of the person" }, "companyWebsite": { "type": "string", "description": "The website of the company" }, "additionalInfo": { "type": "string", "description": "Any additional relevant information about the person" } }, "required": ["fullName", "company"] } schema_str = json.dumps(schema) ``` ```javascript js theme={"system"} const schema = { type: "object", properties: { fullName: { type: "string", description: "The full name of the person" }, company: { type: "string", description: "The company the person works for" }, position: { type: "string", description: "The job title or position of the person" }, linkedInUrl: { type: "string", description: "The LinkedIn profile URL of the person" }, companyWebsite: { type: "string", description: "The website of the company" }, additionalInfo: { type: "string", description: "Any additional relevant information about the person" } }, required: ["fullName", "company"] }; ``` ```python python theme={"system"} def signup_radar(email): # Extract name and domain name_part = email.split('@')[0] domain = email.split('@')[1] # Format name for searching (convert saksena to Saksena) formatted_name = name_part.capitalize() # Determine company from domain (if not common email provider) company_hint = "" common_domains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"] if domain not in common_domains: company_hint = domain.split('.')[0] # Create search query if company_hint: query = f"Find the LinkedIn profile URL for {formatted_name} who works at {company_hint}. Return their full name, current position, and company information." else: query = f"Find the LinkedIn profile URL for someone with the email username {formatted_name}. Return their full name, current position, and company information." # Call Linkup API response = client.search( query=query, depth="deep", # Use deep for more thorough results output_type="structured", structured_output_schema=schema_str ) return response # Example usage from pprint import pprint result = signup_radar("philippe@linkup.so") pprint(result) ``` ```javascript js theme={"system"} async function signupRadar(email) { // Extract name and domain const [namePart, domain] = email.split('@'); // Format name for searching const formattedName = namePart.charAt(0).toUpperCase() + namePart.slice(1); // Determine company from domain (if not common email provider) let companyHint = ""; const commonDomains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"]; if (!commonDomains.includes(domain)) { companyHint = domain.split('.')[0]; } // Create search query let query; if (companyHint) { query = `Find the LinkedIn profile URL for ${formattedName} who works at ${companyHint}. Return their full name, current position, and company information.`; } else { query = `Find the LinkedIn profile URL for someone with the email username ${formattedName}. Return their full name, current position, company, and LinkedIn URL.`; } // Call Linkup API const response = await client.search({ query: query, depth: "deep", // Use deep for more thorough results outputType: "structured", structuredOutputSchema: schema }); return response; } // Example usage signupRadar("philippe@linkup.so").then(console.log); ``` Let's improve our query to get better results: ```python python theme={"system"} def generate_query(email): name_part = email.split('@')[0] domain = email.split('@')[1] # Handle different name formats (snake_case, dot.case, etc.) if "_" in name_part: name_parts = name_part.split("_") formatted_name = " ".join(part.capitalize() for part in name_parts) elif "." in name_part: name_parts = name_part.split(".") formatted_name = " ".join(part.capitalize() for part in name_parts) else: formatted_name = name_part.capitalize() # Determine company from domain common_domains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"] if domain not in common_domains: company = domain.split('.')[0].capitalize() return f"Find the LinkedIn profile URL for this person: {formatted_name} at {company}. If the domain of the email address ({domain}) is not a common email provider, it is probably the name of the company this person works for and {domain} is probably the company website, so search specifically for someone with that name at this company. Return their full name, position, company details, LinkedIn URL, as well as any relevant information you can find about them." else: return f"Find the LinkedIn profile URL for this person with email username {formatted_name}. Return their full name, current position, company, and LinkedIn URL." ``` ```javascript js theme={"system"} function generateQuery(email) { const [namePart, domain] = email.split('@'); // Handle different name formats (snake_case, dot.case, etc.) let formattedName; if (namePart.includes("_")) { formattedName = namePart.split("_") .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } else if (namePart.includes(".")) { formattedName = namePart.split(".") .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } else { formattedName = namePart.charAt(0).toUpperCase() + namePart.slice(1); } // Determine company from domain const commonDomains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"]; if (!commonDomains.includes(domain)) { const company = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1); return `Find the LinkedIn profile URL for this person: ${formattedName} at ${company}. If the domain of the email address (${domain}) is not a common email provider, it is probably the name of the company this person works for and ${domain} is probably the company website, so search specifically for someone with that name at this company. Return their full name, position, company details, and LinkedIn URL, as well as any relevant information you can find about them.`; } else { return `Find the LinkedIn profile URL for this person with email username ${formattedName}. Return their full name, current position, company, and LinkedIn URL.`; } } ``` Here's the complete implementation: ```python python [expandable] theme={"system"} from linkup import LinkupClient import json from pprint import pprint class SignupRadar: def __init__(self, api_key): self.client = LinkupClient(api_key=api_key) self.schema = { "type": "object", "properties": { "fullName": { "type": "string", "description": "The full name of the person" }, "company": { "type": "string", "description": "The company the person works for" }, "position": { "type": "string", "description": "The job title or position of the person" }, "linkedInUrl": { "type": "string", "description": "The LinkedIn profile URL of the person" }, "companyWebsite": { "type": "string", "description": "The website of the company" }, "additionalInfo": { "type": "string", "description": "Any additional relevant information about the person" } }, "required": ["fullName", "company"] } self.schema_str = json.dumps(self.schema) def generate_query(self, email): name_part = email.split('@')[0] domain = email.split('@')[1] # Handle different name formats (snake_case, dot.case, etc.) if "_" in name_part: name_parts = name_part.split("_") formatted_name = " ".join(part.capitalize() for part in name_parts) elif "." in name_part: name_parts = name_part.split(".") formatted_name = " ".join(part.capitalize() for part in name_parts) else: formatted_name = name_part.capitalize() # Determine company from domain common_domains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"] if domain not in common_domains: company = domain.split('.')[0].capitalize() return f"Find the LinkedIn profile URL for this person: {formatted_name} at {company}. If the domain of the email address ({domain}) is not a common email provider, it is probably the name of the company this person works for and {domain} is probably the company website, so search specifically for someone with that name at this company. Return their full name, position, company details, LinkedIn URL, as well as any relevant information you can find about them." else: return f"Find the LinkedIn profile URL for this person with email username {formatted_name}. Return their full name, current position, company, and LinkedIn URL." def lookup(self, email): query = self.generate_query(email) response = self.client.search( query=query, depth="deep", # Use deep for more thorough results output_type="structured", structured_output_schema=self.schema_str ) return response # Example usage if __name__ == "__main__": radar = SignupRadar(api_key="") # Example emails emails = [ "philippe@linkup.so", "boris@linkup.so" ] for email in emails: print(f"\nLooking up: {email}") result = radar.lookup(email) pprint(result) ``` ```javascript js [expandable] theme={"system"} import { LinkupClient } from 'linkup-sdk'; class SignupRadar { constructor(apiKey) { this.client = new LinkupClient({ apiKey: apiKey, }); this.schema = { type: "object", properties: { fullName: { type: "string", description: "The full name of the person" }, company: { type: "string", description: "The company the person works for" }, position: { type: "string", description: "The job title or position of the person" }, linkedInUrl: { type: "string", description: "The LinkedIn profile URL of the person" }, companyWebsite: { type: "string", description: "The website of the company" }, additionalInfo: { type: "string", description: "Any additional relevant information about the person" } }, required: ["fullName", "company"] }; } generateQuery(email) { const [namePart, domain] = email.split('@'); // Handle different name formats (snake_case, dot.case, etc.) let formattedName; if (namePart.includes("_")) { formattedName = namePart.split("_") .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } else if (namePart.includes(".")) { formattedName = namePart.split(".") .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } else { formattedName = namePart.charAt(0).toUpperCase() + namePart.slice(1); } // Determine company from domain const commonDomains = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com", "icloud.com"]; if (!commonDomains.includes(domain)) { const company = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1); return `Find the LinkedIn profile URL for this person: ${formattedName} at ${company}. If the domain of the email address (${domain}) is not a common email provider, it is probably the name of the company this person works for and ${domain} is probably the company website, so search specifically for someone with that name at this company. Return their full name, position, company details, and LinkedIn URL, as well as any relevant information you can find about them.`; } else { return `Find the LinkedIn profile URL for this person with email username ${formattedName}. Return their full name, current position, company, and LinkedIn URL.`; } } async lookup(email) { const query = this.generateQuery(email); const response = await this.client.search({ query: query, depth: "deep", // Use deep for more thorough results outputType: "structured", structuredOutputSchema: this.schema }); return response; } } // Example usage async function main() { const radar = new SignupRadar(''); // Example emails const emails = [ "philippe@linkup.so", "boris@linkup.so" ]; for (const email of emails) { console.log(`\nLooking up: ${email}`); const result = await radar.lookup(email); console.log(JSON.stringify(result, null, 2)); } } main().catch(console.error); ``` ## How it works 1. **Email analysis**: the tool parses the email to extract the username and domain. 2. **Query generation**: it creates a smart search query based on the email components: * Formats the username to handle common patterns (first.last, first\_last) * Uses the domain as a company hint if it's not a common email provider 3. **Structured output**: uses Linkup's structured output feature with a custom schema to ensure consistent, well-formatted results. 4. **Deep search**: uses the `"deep"` `depth` for more comprehensive results. ## Test examples Try the signup radar with these email examples: * [philippe@linkup.so](mailto:philippe@linkup.so) * [boris@linkup.so](mailto:boris@linkup.so) * [sacha@linkup.so](mailto:sacha@linkup.so) * [philippe.mizrahi@gmail.com](mailto:philippe.mizrahi@gmail.com) ## Advanced enhancements For a production version, consider adding: * Other relevant information on your users you receive in the sign up form. These should be added to the prompt * Better manage ambiguity when multiple people could own the same email. Change the prompt and the structured output format to allow for multiple potential people * Error handling for invalid emails or API failures * Rate limiting to manage API usage * Async batch processing for multiple emails ## Conclusion You've now built a "signup radar" using the Linkup API that extracts structured information about users from just their email address. Structured output gives you consistent, well-formatted data ready to be ingested into your systems. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Structured Output Guide Source: https://docs.linkup.so/pages/documentation/tutorials/structured-output-guide How to take advantage of Structured Outputs Linkup's structured output feature allows you to receive responses in a custom format that you define. This is particularly useful when you need to integrate Linkup's responses directly into your application's data structure or when you want to ensure consistency in the response format. ## How it works To use structured outputs: 1. Set `outputType` to `"structured"` in your API request 2. Provide a JSON schema string in the `structuredOutputSchema` parameter 3. The API will return a response that strictly follows your schema Write your query so that its answer contains the information requested in the `structuredOutputSchema` — the system will use the query response to fill the output. ## Basic examples Let's look at some simple examples that demonstrate how to use structured outputs for different use cases:

This example extracts company classification information:

```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_API_KEY") schema = """{ "type": "object", "properties": { "companyName": { "type": "string", "description": "The name of the company" }, "revenueAmount": { "type": "number", "description": "The revenue amount" }, "fiscalYear": { "type": "string", "description": "The fiscal year for this revenue" } } }""" response = client.search( query="What is Microsoft's 2024 revenue?", depth="deep", output_type="structured", structured_output_schema=schema ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_API_KEY', }); const schema = { "type": "object", "properties": { "companyName": { "type": "string", "description": "The name of the company" }, "revenueAmount": { "type": "number", "description": "The revenue amount" }, "fiscalYear": { "type": "string", "description": "The fiscal year for this revenue" } } }; const getCompanyRevenue = async () => { return await client.search({ query: "What is Microsoft's 2024 revenue?", depth: 'deep', outputType: 'structured', structuredOutputSchema: schema }); }; getCompanyRevenue().then(console.log); ``` ```bash curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "q": "What is Microsoft'\''s 2024 revenue?", "depth": "deep", "outputType": "structured", "structuredOutputSchema": "{\"type\":\"object\",\"properties\":{\"companyName\":{\"type\":\"string\",\"description\":\"The name of the company\"},\"revenueAmount\":{\"type\":\"number\",\"description\":\"The revenue amount\"},\"fiscalYear\":{\"type\":\"string\",\"description\":\"The fiscal year for this revenue\"}}}" }' ``` Example response: ```json theme={"system"} { "companyName": "Microsoft", "revenueAmount": 245100000000, "fiscalYear": "2024" } ```

This example shows how to retrieve basic information about a movie:

```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_API_KEY") schema = """{ "type": "object", "properties": { "movieTitle": { "type": "string", "description": "The title of the movie" }, "director": { "type": "string", "description": "The director of the movie" }, "releaseYear": { "type": "integer", "description": "The year the movie was released" }, "boxOfficeRevenue": { "type": "number", "description": "The worldwide box office revenue in USD" } }, "required": ["movieTitle", "director", "releaseYear", "boxOfficeRevenue"] }""" response = client.search( query="What was the director, release year, and box office revenue for The Matrix?", depth="deep", output_type="structured", structured_output_schema=schema ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_API_KEY', }); const schema = { "type": "object", "properties": { "movieTitle": { "type": "string", "description": "The title of the movie" }, "director": { "type": "string", "description": "The director of the movie" }, "releaseYear": { "type": "integer", "description": "The year the movie was released" }, "boxOfficeRevenue": { "type": "number", "description": "The worldwide box office revenue in USD" } }, "required": ["movieTitle", "director", "releaseYear", "boxOfficeRevenue"] }; const getMovieInfo = async () => { return await client.search({ query: "What was the director, release year, and box office revenue for The Matrix?", depth: 'deep', outputType: 'structured', structuredOutputSchema: schema }); }; getMovieInfo().then(console.log); ``` ```bash curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "q": "What was the director, release year, and box office revenue for The Matrix?", "depth": "deep", "outputType": "structured", "structuredOutputSchema": "{\"type\":\"object\",\"properties\":{\"movieTitle\":{\"type\":\"string\",\"description\":\"The title of the movie\"},\"director\":{\"type\":\"string\",\"description\":\"The director of the movie\"},\"releaseYear\":{\"type\":\"integer\",\"description\":\"The year the movie was released\"},\"boxOfficeRevenue\":{\"type\":\"number\",\"description\":\"The worldwide box office revenue in USD\"}}}" }' ``` Example response: ```json theme={"system"} { "movieTitle": "The Matrix", "director": "Lana and Lilly Wachowski", "releaseYear": 1999, "boxOfficeRevenue": 465300000 } ```

This example retrieves weather information for travel planning:

```python python theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_API_KEY") schema = """{ "type": "object", "properties": { "city": { "type": "string", "description": "The name of the city" }, "averageTemperature": { "type": "object", "properties": { "summer": { "type": "number", "description": "Average temperature in summer (°C)" }, "winter": { "type": "number", "description": "Average temperature in winter (°C)" } } }, "annualRainfall": { "type": "number", "description": "Annual rainfall in millimeters" }, "bestTimeToVisit": { "type": "array", "items": { "type": "string" }, "description": "The recommended months to visit" } } }""" response = client.search( query="What is the average temperature, rainfall, and best time to visit Tokyo?", depth="deep", output_type="structured", structured_output_schema=schema ) print(response) ``` ```js js theme={"system"} import { LinkupClient } from 'linkup-sdk'; const client = new LinkupClient({ apiKey: 'YOUR_API_KEY', }); const schema = { "type": "object", "properties": { "city": { "type": "string", "description": "The name of the city" }, "averageTemperature": { "type": "object", "properties": { "summer": { "type": "number", "description": "Average temperature in summer (°C)" }, "winter": { "type": "number", "description": "Average temperature in winter (°C)" } } }, "annualRainfall": { "type": "number", "description": "Annual rainfall in millimeters" }, "bestTimeToVisit": { "type": "array", "items": { "type": "string" }, "description": "The recommended months to visit" } } }; const getWeatherInfo = async () => { return await client.search({ query: "What is the average temperature, rainfall, and best time to visit Tokyo?", depth: 'deep', outputType: 'structured', structuredOutputSchema: schema }); }; getWeatherInfo().then(console.log); ``` ```bash curl theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "q": "What is the average temperature, rainfall, and best time to visit Tokyo?", "depth": "deep", "outputType": "structured", "structuredOutputSchema": "{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"description\":\"The name of the city\"},\"averageTemperature\":{\"type\":\"object\",\"properties\":{\"summer\":{\"type\":\"number\",\"description\":\"Average temperature in summer (°C)\"},\"winter\":{\"type\":\"number\",\"description\":\"Average temperature in winter (°C)\"}}},\"annualRainfall\":{\"type\":\"number\",\"description\":\"Annual rainfall in millimeters\"},\"bestTimeToVisit\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"The recommended months to visit\"}}}" }' ``` Example response: ```json theme={"system"} { "city": "Tokyo", "averageTemperature": { "summer": 26.4, "winter": 6.1 }, "annualRainfall": 1530.8, "bestTimeToVisit": ["March", "April", "October", "November"] } ```
## Advanced example: competitive analysis This example shows how to extract structured competitive analysis information: ```python python [expandable] theme={"system"} from linkup import LinkupClient client = LinkupClient(api_key="YOUR_API_KEY") schema = """{ "type": "object", "properties": { "companies": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "marketPosition": { "type": "object", "properties": { "globalMarketShare": { "type": "number" }, "rankingByRevenue": { "type": "integer" } } }, "strengths": { "type": "array", "items": { "type": "string" } }, "challenges": { "type": "array", "items": { "type": "string" } } } } }, "marketOverview": { "type": "string" } } }""" response = client.search( query="Compare Apple and Samsung in the smartphone market, focusing on their market share, global revenue, key strengths, and primary challenges.", depth="deep", output_type="structured", structured_output_schema=schema ) print(response) ``` ```js js [expandable] theme={"system"} import { LinkupClient } from "linkup-sdk"; const client = new LinkupClient({ apiKey: "YOUR_API_KEY", }); const schema = { type: "object", properties: { companies: { type: "array", items: { type: "object", properties: { name: { type: "string", }, marketPosition: { type: "object", properties: { globalMarketShare: { type: "number", }, rankingByRevenue: { type: "integer", }, }, }, strengths: { type: "array", items: { type: "string", }, }, challenges: { type: "array", items: { type: "string", }, }, }, }, }, marketOverview: { type: "string", }, }, }; const getCompetitiveAnalysis = async () => { return await client.search({ query: "Compare Apple and Samsung in the smartphone market, focusing on their market share, global revenue, key strengths, and primary challenges.", depth: "deep", outputType: "structured", structuredOutputSchema: schema, }); }; getCompetitiveAnalysis().then(console.log); ``` ```bash curl [expandable] theme={"system"} curl --request POST \ --url https://api.linkup.so/v1/search \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "q": "Compare Apple and Samsung in the smartphone market, focusing on their market share, global revenue, key strengths, and primary challenges.", "outputType": "structured", "depth": "deep", "structuredOutputSchema": "{\"type\":\"object\",\"properties\":{\"companies\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"marketPosition\":{\"type\":\"object\",\"properties\":{\"globalMarketShare\":{\"type\":\"number\"},\"rankingByRevenue\":{\"type\":\"integer\"}}},\"strengths\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"challenges\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}},\"marketOverview\":{\"type\":\"string\"}}}" }' ``` Example response: ```json [expandable] theme={"system"} { "companies": [ { "name": "Apple", "marketPosition": { "globalMarketShare": 17.2, "rankingByRevenue": 1 }, "strengths": [ "Strong brand loyalty", "Premium pricing power", "Integrated ecosystem" ], "challenges": [ "Market saturation", "Strong competition in emerging markets", "Supply chain dependencies" ] }, { "name": "Samsung", "marketPosition": { "globalMarketShare": 20.1, "rankingByRevenue": 2 }, "strengths": [ "Diverse product portfolio", "Vertical integration", "Strong presence in emerging markets" ], "challenges": [ "Intense competition at all price points", "Margin pressure in mid-range segment", "Brand perception vs Apple in premium segment" ] } ], "marketOverview": "The smartphone market continues to be highly competitive with a focus on 5G capabilities and AI integration. Premium segment shows steady growth while mid-range experiences intense competition." } ``` ## Making fields required To make sure that the key fields you defined are filled, you can mark them as `required` in your JSON schema. ```json Required Fields Highlight {17} theme={"system"} { "type": "object", "properties": { "movieTitle": { "type": "string", "description": "The title of the movie" }, "director": { "type": "string", "description": "The director of the movie" }, "releaseYear": { "type": "integer", "description": "The year the movie was released" } }, "required": ["movieTitle", "director"] } ``` ## Best practices 1. **Schema design**: * Keep your schema as simple as possible while meeting your needs * Add descriptions to the fields to limit ambiguity * Use appropriate data types (string, number, boolean, etc.) * When in doubt, refer to the [JSON documentation](https://json-schema.org/learn/getting-started-step-by-step) 2. **Query formulation**: * Write your query so that its answer contains the information requested in the `structuredOutputSchema` — the system will use the query response to fill the output * Provide clear context in your query and use explicit instructions ## Common use cases * Company classification and categorization * Competitive analysis * Market research * Product comparisons * Company performance assessments **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Frequently Asked Questions Source: https://docs.linkup.so/pages/faq/faq * Perplexity Sonar is a consumer chatbot wrapped in an API. Linkup is a search API built for production AI. * On Verified SimpleQA, Linkup's [**Search**](/pages/documentation/endpoints/search/overview) endpoint ranks first among sub-second web search APIs with a 94% F-score, ahead of Perplexity Sonar Pro, Exa, and Tavily. On SealQA-0, our [**Research**](/pages/documentation/endpoints/research/overview) endpoint ranks first across the board with 61% accuracy. The [evaluation harness](https://github.com/LinkupPlatform/eval-simpleQA) is open source. * Linkup serves AI agents and pipelines, not end users. The API returns raw context, sourced answers, or structured JSON, depending on what your code needs. * Linkup partners with premium data providers to keep snippet quality and freshness above what general crawlers return. * `"flash"`: our lowest-latency mode. Ranked sources and snippets from the index in under 200 ms. No query interpretation, reformulation, or evaluation. Try queries such as "weather in Paris today". `"flash"` queries cost \$5 per 1,000 queries with `outputType: "searchResults"` (\$6 per 1,000 with `"sourcedAnswer"` or `"structured"`). * `"fast"`: higher-quality one-shot retrieval in about a second. `"fast"` is a single-pass search directly passed to our index. No query interpretation, reformulation, or evaluation. Try queries such as "weather in Paris today". `"fast"` queries cost \$5 per 1,000 queries with `outputType: "searchResults"` (\$6 per 1,000 with `"sourcedAnswer"` or `"structured"`). * `"standard"`: fast and affordable agentic web search. `"standard"` is great for quickly finding grounding data from multiple sources or to run AI analysis on a scraped URL. Try queries such as "What is Microsoft's Q3 2024 revenue?". `"standard"` queries cost \$5 per 1,000 queries with `outputType: "searchResults"` (\$6 per 1,000 with `"sourcedAnswer"` or `"structured"`). * `"deep"`: comprehensive, in-depth web search. `"deep"` handles complex queries that require digging, multi-step information gathering, and reflection. Try queries such as "First, find Linkup website domain. Then scrape the homepage, product page, and about us page. Then search for recent news. Consolidate the output in a comprehensive company profile". `"deep"` queries cost \$50 per 1,000 queries with `outputType: "searchResults"` (\$55 per 1,000 with `"sourcedAnswer"` or `"structured"`). We post key changes to the API on our [Changelog](../../pages/changelog/). We also discuss them on our [Discord](https://discord.gg/HHv29dPa) and on [X](https://x.com/Linkup_platform). You can send us an email at [support@linkup.so](mailto:support@linkup.so) or ask us directly on [Discord](https://discord.gg/HHv29dPa). Linkup runs a proprietary retrieval model trained for high-precision search, combined with selected third-party LLMs for query interpretation and synthesis on `"standard"` and `"deep"`. The model mix is tuned for accuracy and latency on the SimpleQA and SealQA benchmarks; we change the underlying components when a measurable improvement is available. If you get this type of error, make sure you have installed the correct package: `pip install linkup-sdk` You can reach out to [support@linkup.so](mailto:support@linkup.so) if you still experience issues. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Agno Source: https://docs.linkup.so/pages/integrations/agno/agno How to use Linkup with Agno ## Overview Linkup can be integrated with [Agno](https://www.agno.com) as a tool to provide real-time web search capabilities to your Agno AI agents. ## Installation 1. Get your Linkup API Key: Create a Linkup account for free to get your API key. 2. Set the API key in your environment: ```shell theme={"system"} export LINKUP_API_KEY=your_api_key_here ``` 1. Install the Linkup SDK using pip: ```shell theme={"system"} pip install linkup-sdk ``` 1. Install Agno using pip: ```shell theme={"system"} pip install agno ``` ## Example usage The following agent will search the web for the latest news in French politics and print the response. ```python python theme={"system"} from agno.agent import Agent from agno.tools.linkup import LinkupTools agent = Agent(tools=[LinkupTools()], show_tool_calls=True) agent.print_response("What's the latest news in French politics?", markdown=True) ``` ## Toolkit functions | Function | Description | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `web_search_with_linkup` | Searches the web for a query using Linkup API. Takes a query string and optional depth/output\_type parameters. Returns search results as a string. | ## Toolkit parameters | Parameter | Type | Default | Description | | ------------- | ---------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `api_key` | `Optional[str]` | `None` | API key for authentication. If not provided, will check LINKUP\_API\_KEY environment variable. | | `depth` | `Literal["flash", "fast", "standard", "deep"]` | `"standard"` | Depth of the search. Use `"flash"` for lowest latency (under 200 ms, no LLM), `"fast"` for higher-quality one-shot retrieval (\~1s, no LLM), `"standard"` for fast agentic web search, or `"deep"` for comprehensive, in-depth web search. | | `output_type` | `Literal["sourcedAnswer", "searchResults"]` | `"searchResults"` | Type of output. `"sourcedAnswer"` provides a comprehensive natural language answer to the query along with citations to the source material. `"searchResults"` returns the raw search context data without synthesis. | You are now ready to use Linkup in Agno as a tool for your agents ! You can check the [Agno documention](https://docs.agno.com/introduction/agents) for more information about agents and tools. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Baseten Source: https://docs.linkup.so/pages/integrations/baseten/baseten Use Linkup as a real-time web search tool for models served on Baseten's serverless inference platform Baseten provides serverless GPU inference for open-source models with no infrastructure to manage. With Linkup, you can ground these models in real-time web data, giving them the ability to retrieve current facts, news, and source-backed information beyond their training data. This guide demonstrates the integration using GLM 5, but the same approach works with any model available on Baseten that supports function calling. Create a Linkup account for free to get your API key. Navigate to your Baseten settings, select API Keys, click Create API key, and copy the generated key. Initialize your project and create a virtual environment: ```bash theme={"system"} mkdir baseten-linkup cd baseten-linkup python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate ``` Install dependencies: ```bash theme={"system"} pip install openai linkup-sdk ``` Configure your API keys as environment variables: ```bash Mac / Linux theme={"system"} export BASETEN_API_KEY=paste_your_baseten_key_here export LINKUP_API_KEY=paste_your_linkup_key_here ``` ```bash Windows theme={"system"} set BASETEN_API_KEY=paste_your_baseten_key_here set LINKUP_API_KEY=paste_your_linkup_key_here ``` Create a file named `agent.py` and add the following code: ```python agent.py theme={"system"} import os import json from datetime import datetime from openai import OpenAI from linkup import LinkupClient client = OpenAI( api_key=os.environ.get("BASETEN_API_KEY"), base_url="https://inference.baseten.co/v1" ) linkup_client = LinkupClient(api_key=os.environ.get("LINKUP_API_KEY")) tools = [{ "type": "function", "function": { "name": "search_web", "description": "Search the web in real time. Use this tool whenever the user needs trusted facts, news, or source-backed information. Returns comprehensive content from the most relevant sources.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query" } }, "required": ["query"] } } }] def main(): print("--- GLM 5 + Linkup ---") print("Type 'quit' to exit.\n") today_str = datetime.now().strftime("%B %d, %Y") system_prompt = ( f"You are a helpful assistant. Today is {today_str}. " f"Use web search when you need current information. " f"Prefer searching over relying on your training data for anything that could be outdated." ) history = [{"role": "system", "content": system_prompt}] while True: try: user_input = input("You: ") if user_input.lower() in ["quit", "exit"]: print("Goodbye!") break history.append({"role": "user", "content": user_input}) response = client.chat.completions.create( model="zai-org/GLM-5", messages=history, tools=tools, tool_choice="auto" ) message = response.choices[0].message while message.tool_calls: history.append(message) for tc in message.tool_calls: q = json.loads(tc.function.arguments)["query"] print(f"Searching using Linkup: {q}...") try: result = linkup_client.search( query=q, depth="standard", output_type="searchResults" ) content = "\n\n".join( f"{r.name}\n{r.url}\n{r.content}" for r in result.results ) except Exception as e: content = f"Search error: {e}" history.append({ "role": "tool", "tool_call_id": tc.id, "content": content }) response = client.chat.completions.create( model="zai-org/GLM-5", messages=history, tools=tools, tool_choice="auto" ) message = response.choices[0].message print(f"Agent: {message.content}\n") history.append(message) except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main() ``` ```bash theme={"system"} python agent.py ``` **Internal knowledge (no tool call):** ``` You: What is the definition of philosophy? Agent: Philosophy is the study of fundamental questions about existence, knowledge, values, reason, mind, and language... ``` **Tool-augmented reasoning:** ``` You: What are the latest books published on logic? Searching using Linkup: latest logic books 2025... Agent: Synthesizes a response citing recent publications found via Linkup. ``` For more information, visit: * [Baseten Documentation](https://docs.baseten.co/) **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Cargo Source: https://docs.linkup.so/pages/integrations/cargo/cargo Search the web and enrich records in Cargo with Linkup ## Overview [Cargo](https://www.getcargo.ai) is go-to-market infrastructure for revenue teams, optimized for enriching records, researching accounts, and running that work from a play. You can connect a play to web search through Linkup to pull in live web information, company research, and structured answers. Cargo documents the connector at [Linkup on Cargo](https://docs.getcargo.ai/integration/linkup). ## Installation Create a Linkup account for free to get your API key. In Cargo, open the Linkup integration and paste your API key. Cargo sends that key with each action. Add the Search action or the Instruct action to your play, fill in the fields below, and run the step. ## Actions ### Search Searches the web with a natural-language query. This action calls the [**Search**](/pages/documentation/endpoints/search/overview) endpoint. | Field | Description | | ------------ | ------------------------------------------------------------------------- | | Query | Natural-language question or search query | | Search depth | `"standard"` for a quicker search, or `"deep"` for a more thorough search | ### Instruct Returns a sourced answer or a structured response for a question. | Field | Description | | ------------------------ | --------------------------------------------------- | | Query | Natural-language question | | Search depth | `"standard"` or `"deep"` | | Output type | A sourced answer, or a structured response | | Structured output schema | JSON schema used when the output type is structured | Use a structured response when you need specific fields, such as company facts, product details, or competitive notes. Cargo lists connector credit costs on the [Linkup on Cargo](https://docs.getcargo.ai/integration/linkup) page. Linkup bills the underlying API calls as described on the [pricing](/pages/documentation/platform/pricing) page. ## Use cases * **Company research.** Pull current information about companies, products, and news into a play. * **Competitive intelligence.** Research competitors and market trends with sourced answers. * **Data enrichment.** Fill records with live web data using structured output. ## Best practices 1. Use `"deep"` when the question needs a more thorough pass. 2. For structured output, define a precise JSON schema so each run returns the same fields. 3. Write a specific query. Extra context in the question improves the result. You are all set to use Linkup in your Cargo workflows. Visit the [Search overview](/pages/documentation/endpoints/search/overview) to learn more about Linkup parameters. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Cerebras Source: https://docs.linkup.so/pages/integrations/cerebras Use Linkup as a real-time web search tool for models served on Cerebras' ultra-fast inference platform Cerebras provides ultra-fast inference (3,000+ tokens/sec) for open-source models. With Linkup, you can ground these models in real-time web data, giving them the ability to retrieve current facts, news, and source-backed information beyond their training data. This guide demonstrates the integration using GPT-OSS 120B, but the same approach works with any model available on Cerebras that supports function calling. Create a Linkup account for free to get your API key. Create a Cerebras account for free to get your API key. Install the required packages: ```bash theme={"system"} pip install linkup-sdk cerebras-cloud-sdk ``` Set up your clients: ```python theme={"system"} from cerebras.cloud.sdk import Cerebras from linkup import LinkupClient import json from datetime import datetime cerebras_client = Cerebras(api_key="your_cerebras_api_key") linkup_client = LinkupClient(api_key="your_linkup_api_key") ``` Tell Cerebras about your search function: ```python theme={"system"} tools = [{ "type": "function", "function": { "name": "search_web", "description": "Search the web for current information. Returns comprehensive content from relevant sources.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query" } }, "required": ["query"] } } }] ``` Put it all together in a conversational loop: ```python theme={"system"} system_prompt = f"You are a helpful assistant. Today is {datetime.now().strftime('%B %d, %Y')}. Use web search when you need current information." messages = [{"role": "system", "content": system_prompt}] print("Chatbot ready! Type 'quit' to exit.\n") while True: user_input = input("You: ").strip() if not user_input or user_input.lower() == "quit": break messages.append({"role": "user", "content": user_input}) try: response = cerebras_client.chat.completions.create( model="gpt-oss-120b", messages=messages, tools=tools ) message = response.choices[0].message while message.tool_calls: messages.append(message) for tool_call in message.tool_calls: args = json.loads(tool_call.function.arguments) print(f"Searching with Linkup: {args['query']}...") linkup_response = linkup_client.search( query=args["query"], depth="standard", output_type="searchResults" ) search_results = json.dumps( [{"content": r.content} for r in linkup_response.results] ) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": search_results }) response = cerebras_client.chat.completions.create( model="gpt-oss-120b", messages=messages, tools=tools ) message = response.choices[0].message print(f"\nAssistant: {message.content}\n") messages.append({"role": "assistant", "content": message.content}) except Exception as e: print(f"Error: {e}\n") messages.pop() ``` For more information, visit: * [Cerebras Inference Documentation](https://inference-docs.cerebras.ai/) **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Chainlit Chatbot Source: https://docs.linkup.so/pages/integrations/chatbot/build-your-chatbot Connect your Chainlit chatbot to the internet with Linkup Search the Web Linkup is natively integrated in [Chainlit](https://docs.chainlit.io), an open-source Python framework specifically designed for building production-ready Conversational AI applications. You can now deploy an open source chatbot with "search the web" capabilities, in minutes. This chatbot will feature: * Real-time web search using Linkup * A web interface with Chainlit, supporting conversation context management and message streaming ## Implementation Guide Follow the Chainlit documentation to set up your chatbot application. Visit the [Chainlit getting started guide](https://docs.chainlit.io/get-started/overview) to learn the basics of creating a Chainlit application. This will require [an Anthropic API key](https://docs.anthropic.com/en/api/admin-api/apikeys/get-api-key) or an API key from any LLM provider. Add web search to your Chainlit app by following the integration steps in the [Chainlit cookbook](https://github.com/Chainlit/cookbook/tree/main/ai-web-search-linkup). Your chatbot will be able to run real-time web searches with Linkup. Requires a Linkup API key. Create a Linkup account for free to get your API key. **Need help?** Email `support@linkup.so`, ping us on [Discord](https://discord.com/invite/9q9mCYJa86), or [talk to us](https://calendar.app.google/zNUZz7RxgMMk9pKW7). # Clay Source: https://docs.linkup.so/pages/integrations/clay/clay Enrich Companies in Clay using Linkup Web Search ## Overview You can connect a [Clay](https://clay.com) table to web search through Linkup, which can be used as an enrichment provider to get contextual information from the internet for your prospects and leads. Below is a short Youtube Tutorial: