Quickstart
Grab a free key from your dashboard. You get 3,000 credits a month, no card. Send the key as an x-api-key header and hit one endpoint.
Here is what actually comes back. Pick an endpoint and see a real, recent response (phones shown masked, the API returns them in full):
You pay one credit per successful search, however many results come back. Maps is the one exception, it bills one credit per business returned. Empty searches are free, and the credits you buy never expire. Every response is the same stable JSON shape, and it matches serper's, so you can point existing code at us without rewriting your parser.
| credits | price | per 1,000 |
|---|---|---|
| 3,000 every month | Free, no card | $0 |
| 25,000 | $49 | $1.96 |
| 100,000 | $149 | $1.49 |
| 500,000 | $549 | $1.10 |
| 2,500,000 | $1,999 | $0.80 |
Packs continue up to 250 million credits for $100,000, which is $0.40 per 1,000. Prices ex-tax.
One endpoint, one parameter that picks the Google surface. Change type, keep everything else the same.
The surfaces you can ask for:
| type | what you get |
|---|---|
web | Google web search: organic results, people-also-ask, related searches |
maps | Local businesses: name, address, phone, website, rating, review count |
places | A single place in a lean, serper-exact shape |
news | Google News: title, source, date, and a real hero image |
shopping | Google Shopping products with prices and sellers |
images | Google Images results |
videos | Google Videos with direct thumbnails |
scholar | Google Scholar papers and citations |
patents | Google Patents results |
autocomplete | Google autocomplete suggestions for a query |
webpage | Any URL as clean text, metadata, and JSON-LD, ready to feed an AI (RAG) |
lens | Reverse image search: pass an image url and get the pages where that image appears, with titles and links |
reviews | Google reviews for a business, with sorting and pagination |
Common parameters
| param | what it does |
|---|---|
q | Your search query. Required for most surfaces. |
gl | Country code, like us or gb. |
hl | Language code, like en. |
location | Where to search from, for maps and places, like Miami, FL. |
limit | For type=maps: how many businesses to return. Default 20, max 100. |
num | For type=reviews: how many reviews per page. Default 20, max 50. For type=images: how many images to return. Leave it off and you get the full set, which is usually about 99. |
num | For type=reviews: how many reviews per page. Max 50. |
page | Which page of results to return. |
The full machine-readable spec lives at /v1/openapi.json. Point your tooling or your agent at it.
Public, logged-out LinkedIn data through a second endpoint with the same clean shape. Pick what you want with type, pass a profile or company URL (or keywords), and get JSON back in about a second.
The types you can ask for:
One rule for the names: singular gets the one you point at, plural finds many. person takes a URL and returns that person; people takes keywords and returns a list. Same for job and jobs. Older names (profile, search) still work exactly as before and always will.
| type | input | what you get |
|---|---|---|
person | a /in/ URL | Full public profile: name, headline, about, location, followers, connections, work history, education, and skills. profile is the same thing and keeps working. |
people | a name, or a profession | Find people two ways. Give a name (keywords=bill gates, optionally location=Canada) for up to 50 matches. Or give a profession (title=accountants, optionally location=Chicago, IL) to find people offering that service, with the services each one lists. search is the same endpoint and keeps working. |
company | a /company/ URL | Company info: industry, size, employee count, headquarters, website, specialties, similar companies |
job | a job URL | Full detail for one job: title, company, seniority, description (text and HTML), job functions, structured location, salary when shown, and whether the posting is still open |
jobs | keywords (+ location) | Job search results. Same fields as job on every row. |
posts | a profile or company URL | Recent posts from a person OR a company: text, likes, date, type, and link. Add comments=true for the comment threads. |
refresh | a /in/ URL | Fast freshness check: name, current company and school (with numeric ids), and whether the profile is accessible. Built to keep a database current at scale. Not billed when the profile is not accessible. |
Parameters
| param | what it does |
|---|---|
url | The LinkedIn URL, for profile, company, and posts. |
keywords | What to search for. Required on type=search. Optional on type=jobs — pass filters with no keywords at all and you get every job that matches them. |
sortBy | On type=jobs results are newest first by default. Pass sortBy=relevance for LinkedIn's own relevance order. |
location | Optional location filter for type=jobs. For more than one place, separate with a semicolon (location=London;Berlin) or repeat the parameter. Use a semicolon, not a comma, so a location like New York, NY stays in one piece. Up to 3 locations per call. On type=search it narrows a name search to a country (for example location=Canada). |
company | On type=jobs: only jobs at this company. Takes a name, a /company/ URL, or a numeric id. |
title | On type=people: find people by profession, e.g. title=accountants or title=real estate agents. Use with location for one city (Chicago, IL); leave location off and we search nationally and across cities to fill your requested number. Professions come from LinkedIn's own service categories, so an unknown one returns nothing rather than a wrong guess. profession is an accepted alias. |
postedWithin | On type=jobs: 24h, week, or month. |
employmentType | On type=jobs: Full-time, Part-time, Contract, Temporary, Internship, Volunteer, or Other. Pass several separated by commas (employmentType=Full-time,Contract) to match any of them. |
seniority | On type=jobs: Internship, Entry level, Associate, Mid-Senior level, Director, Executive, or Not Applicable. Comma-separate several to match any of them. |
daysSincePostedMin / daysSincePostedMax | On type=jobs: filter by how long ago a job was posted, in days. Every job also returns postedAgeText plus a postedDaysMin/postedDaysMax range, because LinkedIn publishes a bucketed age ("2 weeks ago") rather than an exact date. The range is calibrated, so the bounds are real. |
titleInclude | On type=jobs: keep only postings whose title contains any of these. Comma-separated (titleInclude=engineer,developer). |
titleExclude | On type=jobs: drop postings whose title contains any of these. Comma-separated (titleExclude=senior,staff). Exclusions win over includes. |
descriptionKeywords | On type=jobs: keep postings whose title or description contains any of these. Comma-separated. |
locationExclude | On type=jobs: drop postings in these places. Semicolon-separated, because a place name contains a comma (locationExclude=New York, NY;Austin, TX). |
hasRecruiter | On type=jobs: true keeps only postings that name a contact you can message, false keeps only those that do not. Those postings also return a jobPoster object with the person's name, headline, and profile URL. LinkedIn publishes a poster on roughly one job in five. |
count | On type=jobs: count=true returns just totalAvailable — how many jobs match your filters — and no rows. It is free; the response carries charged: 0. Use it to size a search before you run it. |
num / limit | How many results to return; both spellings work. jobs defaults to 25 (max 100), posts to 50 (max 100), search to 10 (max 15), people to 10 (max 50). |
email | Set email=true on type=profile to add workEmail, emailStatus (verified, pattern-likely, or unknown) and emailConfidence. |
domain | With email=true: pass the company site, like acme.com, when you already know it. |
companyId | Set companyId=true on type=profile to add LinkedIn's numeric company ids to the work history. Same idea for schoolId=true and education. |
schema | Set schema=scrapin on profile or company to get a ScrapIn-shaped response, so an existing pipeline drops in unchanged. |
employees=true | On type=company: also return a sample of employees. |
comments=true | On type=posts: also fetch comment threads. Post text, comment counts, reaction types and images are returned by default. |
enrich=true | On type=search: return the full profile for every person found, not just the summary. |
Credits
One credit per successful call, the same as the rest of the API. The one exception is an enriched people search (type=search&enrich=true): because each result comes back as a complete profile, it bills one credit per profile returned, exactly what you would pay to fetch those profiles directly. Private or restricted pages return an empty result and are free.
Bulk lists and webhooks
When you have a list of profiles instead of one URL, send the whole list in a single request. Every row is fetched in parallel on our side, results come back in the same order you sent them, and one dead profile never fails the rest. Billing stays the same: you pay per row that returns real data, and rows that come back empty or not accessible are free.
Up to 100 URLs run like this, in one round trip. The response is an array in your input order, each row carrying either data or its own error.
Async with a webhook, up to 10,000 rows
For bigger lists, add a webhook URL. We answer immediately with a job id, do the work in the background, and POST the full results to your webhook when the batch finishes. We keep only the counts, never your results.
You get back 202 with a jobId right away. When the batch completes, your webhook receives one POST with the same payload a sync call returns, plus the jobId. The header X-Crustapi-Job carries the job id so you can match deliveries. If your endpoint is down we retry once, and you can always check progress yourself:
The status call returns counts only: how many rows, how many succeeded, how many were charged, and whether the webhook was delivered. Your data lives at your webhook, not with us.
The details that matter
| rule | how it works |
|---|---|
| Types | refresh (recommended for keeping a database current) or profile. |
| Limits | 100 URLs per sync call, 1,000 per async call with a webhook. |
| Billing | 1 credit per row that returns data. Not-accessible rows are free. A malformed URL is an error row, never a charge. |
| Order | Results always match your input order, so you can zip them straight onto your rows. |
| Webhook rules | Public https URL, no credentials in it. We POST JSON once, retry once on failure, and follow no redirects. |
| Privacy | Async results are delivered and forgotten. We store counts, not content. |
CLI
Prefer the terminal? Install the CLI and get the same data from your shell, as JSON or CSV.
Output is JSON by default and pipes cleanly, the status line goes to stderr so your pipes stay clean. Pass --csv for CSV. It's on npm as crustapi-cli.
MCP for AI assistants
Give Claude Desktop, Cursor, Cline, or any MCP client live Google data. There's nothing to install, npx runs it. Add this to your client config and restart it.
Three tools appear. search is the whole menu behind one call, scrape_webpage turns any URL into clean text you can feed straight to an AI (RAG), and get_reviews pulls Google reviews for a business. The package is on npm as crustapi-mcp.
Integrations
CrustAPI is one HTTP endpoint, so it drops into any tool that can call a URL. Below is the copy-paste setup for the platforms people ask about most.
Clay
Enrich any Clay table with live Google data. Add an HTTP API enrichment column and point it here.
Map a table column into {{Company}} (or any query), then pull fields like places[0].website, places[0].phone, and places[0].reviewsCount into columns. Swap type=maps for web, news, or reviews to enrich with anything Google knows.
Any HTTP node (n8n, Make, Zapier, Retool)
Same idea everywhere: a GET request to /v1/search with your key in the x-api-key header returns clean JSON you can map to fields. No SDK required.
AI agent frameworks
LangChain, CrewAI, Haystack, and LlamaIndex all load an MCP server natively, so the MCP setup above makes CrustAPI a tool in any of them. Native packages already shipped: langchain-crustapi on PyPI and n8n-nodes-crustapi on npm. For anything else, email support@crustapi.com.
Agent payments (x402)
Your agent doesn't need a signup, a card, or a key to start. It can buy its own credits with x402, the open HTTP payment standard. If your agent framework already speaks x402, this works with no extra code from you.
Here's the whole handshake.
- Your agent calls
POST /v1/x402/topup?pack=agentwith no key. - We answer
402 Payment Requiredwith the amount, where to pay, and the USDC contract on Base. - The agent's wallet signs a gasless USDC authorization (EIP-3009) and sends the request again with the signature.
- We verify and settle it on-chain, then hand back a real API key that's already loaded with credits.
From there the key works like any other. The agent calls /v1/search and spends the credits it just bought.
The 402 challenge
Call the top-up endpoint with no key and you get back the payment terms.
After payment
Your x402 client signs the authorization and retries. We settle it and return a key that's ready to use.
A few things worth knowing:
- Payment is USDC on Base, and it's gasless. Your agent signs an authorization, so it doesn't need ETH to pay for gas.
- The agent pack is a $5 first deposit for 2,500 credits. The bigger packs work the same way, just pass a different pack as you scale up.
- Credits from an x402 payment are identical to credits bought with a card. One credit per successful search, and empty results are free.
Ask your AI
Still have questions? Copy this md file with detailed product description to ask your agent about it.
Agents can fetch it directly at crustapi.com/llms.txt.
Stuck on something that isn't here? Email support@crustapi.com and a human will answer.