Find companies with the Search API
Find matching companies, then retrieve their details.
You have looked up a company you already know. Now find companies that match criteria, then use a returned UUID to retrieve one company's details.
In this walkthrough, you will find organizations classified as SaaS with headquarters in San Francisco, California. You will request up to three matches and enrich one of them. This is a small sample, not a complete market list.
Before you start: complete Understand the Crunchbase data model. Use the same provisioned REST API key and CRUNCHBASE_API_KEY environment variable. The cURL commands use bash/zsh; Windows users can use the Python example below. Check your licensed package’s reference before changing collections, fields, or operators.
Set your API key in the terminal
macOS/Linux:
export CRUNCHBASE_API_KEY='YOUR_API_KEY'Windows PowerShell, for the Python example:
$env:CRUNCHBASE_API_KEY='YOUR_API_KEY'Replace YOUR_API_KEY with your provisioned key. See Make your first API request for key retrieval.
The path you will follow
Choose filter UUIDs → Search organizations → Copy a returned organization UUID → Look up that organization.
The category and location UUIDs identify your search criteria. The organization UUID identifies a company in the results. Keep these roles separate.
1. Choose the industry and location
Use Autocomplete to find identifiers for the criteria you want. First, request category suggestions for SaaS:
curl --request GET \
'https://api.crunchbase.com/v4/data/autocompletes?query=SaaS&collection_ids=categories&limit=5' \
--header "X-cb-user-key: $CRUNCHBASE_API_KEY" \
--header 'Accept: application/json'Then request city suggestions for San Francisco:
curl --request GET \
'https://api.crunchbase.com/v4/data/autocompletes?query=San%20Francisco&collection_ids=location.cities&limit=5' \
--header "X-cb-user-key: $CRUNCHBASE_API_KEY" \
--header 'Accept: application/json'Check: both requests return HTTP 200. Inspect each suggestion's identifier and short_description before selecting it. Autocomplete provides suggestions, not a guaranteed exact match. Several cities share the name San Francisco.
These are the selections used below, verified from the responses:
| Criterion | Confirm this result | Copy identifier.uuid |
|---|---|---|
| Industry | value: SaaS, entity_def_id: category, permalink: saas-5c4e | 5c4e6926-5ff7-b188-0892-c8eb036c5ace |
| Headquarters city | value: San Francisco, permalink: san-francisco-california; description identifies California, United States | 528f5e3c-90d1-1111-5d1c-2e4ff979d58e |
In Autocomplete, the path is entities[i].identifier.uuid, where i is the index of the suggestion you selected. Do not automatically select the first result when adapting this example. If no suggestion matches the intended category or city, refine the query and stop until you can resolve the ambiguity.
2. Find organizations in one industry
Search uses POST with a JSON body. Start with one predicate: the categories field must include the SaaS category UUID.
curl --request POST \
'https://api.crunchbase.com/v4/data/searches/organizations' \
--header "X-cb-user-key: $CRUNCHBASE_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
"field_ids": [
"identifier",
"short_description",
"categories",
"location_identifiers"
],
"query": [
{
"type": "predicate",
"field_id": "categories",
"operator_id": "includes",
"values": [
"5c4e6926-5ff7-b188-0892-c8eb036c5ace"
]
}
],
"limit": 3
}'| Request part | What it controls |
|---|---|
field_ids | Which fields to return for each result. It does not filter which organizations match. |
query | The criteria an organization must match. Each object is a predicate. |
field_id | The field to test; here, categories. |
operator_id: includes | Match an organization containing any of the listed category values. Here there is one value. |
values | The selected category UUID, not the display name SaaS. |
limit: 3 | Return at most three organizations on this page. |
Check: expect HTTP 200 with an entities array. Each result has its fields under properties, as in Entity Lookup, but Search wraps multiple records in entities. Requested category and location fields help you inspect why an organization matches.
3. Add the headquarters location
Keep the same selected fields and page size. Add a second predicate for location_identifiers:
curl --request POST \
'https://api.crunchbase.com/v4/data/searches/organizations' \
--header "X-cb-user-key: $CRUNCHBASE_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
"field_ids": [
"identifier",
"short_description",
"categories",
"location_identifiers"
],
"query": [
{
"type": "predicate",
"field_id": "categories",
"operator_id": "includes",
"values": [
"5c4e6926-5ff7-b188-0892-c8eb036c5ace"
]
},
{
"type": "predicate",
"field_id": "location_identifiers",
"operator_id": "includes",
"values": [
"528f5e3c-90d1-1111-5d1c-2e4ff979d58e"
]
}
],
"limit": 3
}'Separate predicates are combined with AND: an organization must be classified as SaaS and have the selected headquarters location. This is a headquarters filter, not a test for any office or customers in that city.
Within an includes predicate, multiple values are alternatives. For example, two category UUIDs mean either category, still AND the location predicate. Check supported operators for each field before adapting the query. For OR across separate predicates, see Combining filters: AND and OR.
Check: inspect entities. A response excerpt from a live request is shown below. Other entities, fields within identifiers, categories, and location levels are omitted; your result order, names, and descriptions may change.
{
"entities": [
{
"uuid": "cf2c678c-b81a-80c3-10d1-9c5e76448e51",
"properties": {
"identifier": {
"uuid": "cf2c678c-b81a-80c3-10d1-9c5e76448e51",
"value": "OpenAI",
"entity_def_id": "organization"
},
"short_description": "OpenAI develops artificial intelligence models and deploys machine learning software tools, including ChatGPT.",
"categories": [
{
"uuid": "5c4e6926-5ff7-b188-0892-c8eb036c5ace",
"value": "SaaS"
}
],
"location_identifiers": [
{
"uuid": "528f5e3c-90d1-1111-5d1c-2e4ff979d58e",
"value": "San Francisco",
"location_type": "city"
}
]
}
}
]
}For the first result, read:
entities[0].uuid: the organization UUID to use for the lookup.entities[0].properties.identifier.value: its display name.entities[0].properties.categories: industry identifiers.entities[0].properties.location_identifiers: headquarters location identifiers.
Unlike Autocomplete, the requested Search fields are inside properties. The top-level result uuid and properties.identifier.uuid identify the same organization.
If entities is [], this request returned no matches; do not attempt the lookup. If the array or required UUID is missing, stop and inspect the response. Missing optional fields are not proof that the company lacks that attribute. Adding a filter narrows the matching criteria; do not use the length of a three-item page to measure the size of the market.
4. Retrieve details for one result
Copy an organization UUID from your Search response. This is the same Entity Lookup operation used in the previous guide, now starting from a discovered company:
COMPANY_UUID='cf2c678c-b81a-80c3-10d1-9c5e76448e51'
curl --request GET \
"https://api.crunchbase.com/v4/data/entities/organizations/$COMPANY_UUID?field_ids=identifier,short_description,website_url" \
--header "X-cb-user-key: $CRUNCHBASE_API_KEY" \
--header 'Accept: application/json'The assignment uses the UUID from the preceding excerpt. Replace it with a returned UUID from your own response if different.
Response excerpt; other identifier fields omitted:
{
"properties": {
"identifier": {
"uuid": "cf2c678c-b81a-80c3-10d1-9c5e76448e51",
"value": "OpenAI"
},
"short_description": "OpenAI develops artificial intelligence models and deploys machine learning software tools, including ChatGPT.",
"website_url": "https://openai.com"
}
}Check: HTTP 200 and properties.identifier.uuid matches the organization UUID you copied. Read properties.website_url for its website. You can request cards in Entity Lookup as described in the data-model guide; do not put card_ids into this Search body.
You have now connected criteria → matching organization → company details. Store the organization UUID alongside your result. Keep missing optional values missing.
Run the search and lookup in Python
This optional Python 3 example uses only the standard library. It reuses the two verified filter UUIDs from step 1, fetches one page of up to three organizations, and enriches the first returned organization. It does not resolve new criteria, paginate, or produce a complete market list.
- Save the code as
search_companies.py. - Set
CRUNCHBASE_API_KEYin the same terminal. - Run
python3 search_companies.py(orpythonif that is your Python 3 command).
Show the complete Python example
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from uuid import UUID
# Verified choices from the Autocomplete step, not organization UUIDs.
SAAS = '5c4e6926-5ff7-b188-0892-c8eb036c5ace'
SAN_FRANCISCO = '528f5e3c-90d1-1111-5d1c-2e4ff979d58e'
def request(path, body=None):
data = json.dumps(body).encode() if body is not None else None
req = Request('https://api.crunchbase.com/v4/data/' + path, data=data,
headers={'X-cb-user-key': os.environ['CRUNCHBASE_API_KEY'],
'Accept': 'application/json',
'Content-Type': 'application/json'})
with urlopen(req, timeout=30) as response:
result = json.load(response)
if not isinstance(result, dict):
raise ValueError('Expected a JSON object.')
return result
def main():
if not os.environ.get('CRUNCHBASE_API_KEY', '').strip():
raise ValueError('Set CRUNCHBASE_API_KEY to your provisioned key first.')
body = {
'field_ids': ['identifier', 'short_description', 'categories',
'location_identifiers'],
'query': [
{'type': 'predicate', 'field_id': 'categories',
'operator_id': 'includes', 'values': [SAAS]},
{'type': 'predicate', 'field_id': 'location_identifiers',
'operator_id': 'includes', 'values': [SAN_FRANCISCO]},
],
'limit': 3,
}
entities = request('searches/organizations', body).get('entities')
if not isinstance(entities, list):
raise ValueError('Missing entities array; inspect the response.')
if not entities:
print('No matches returned. No entity lookup was attempted.')
return
first = entities[0]
if not isinstance(first, dict) or not isinstance(first.get('uuid'), str):
raise ValueError('First result is missing its UUID.')
company_uuid = first['uuid']
UUID(company_uuid)
search_props = first.get('properties')
identifier = search_props.get('identifier') if isinstance(search_props, dict) else None
if not isinstance(identifier, dict) or identifier.get('entity_def_id') != 'organization' or identifier.get('uuid') != company_uuid:
raise ValueError('Expected a matching organization identifier.')
params = urlencode({'field_ids': 'identifier,short_description,website_url'})
detail = request('entities/organizations/' + company_uuid + '?' + params)
props = detail.get('properties')
identity = props.get('identifier') if isinstance(props, dict) else None
if not isinstance(identity, dict) or identity.get('uuid') != company_uuid:
raise ValueError('Lookup identifier does not match the search result.')
print(json.dumps({
'scope': 'First search page only; one company enriched',
'returned_on_this_page': len(entities),
'company_uuid': company_uuid,
'company': identity.get('value'),
'website': props.get('website_url'),
'description': props.get('short_description'),
}, indent=2))
if __name__ == '__main__':
try:
main()
except HTTPError as error:
print(f'Stopped: HTTP {error.code}. Inspect the status before retrying.', file=sys.stderr)
sys.exit(1)
except (URLError, TimeoutError):
print('Stopped: network error or timeout.', file=sys.stderr)
sys.exit(1)
except ValueError as error:
print(f'Stopped: {error}', file=sys.stderr)
sys.exit(1)The output is a summary assembled by the script, not an API response. It includes the number of organizations on this page and one company's UUID, name, website, and description. Missing optional values remain null. An empty results array prints a no-matches message without making a lookup. HTTP errors, invalid required identifiers, and timeouts stop the script; it does not retry automatically.
When you need another page
For the next Search page, copy the last result's uuid into a top-level after_id property in the same JSON request body. Keep query, field_ids, order (if supplied), and limit unchanged. Do not use the first result chosen for enrichment as the cursor unless it is also the last result on the page.
Do not send before_id together with after_id. Continue until the API returns an empty entities array; do not stop solely because a page is short or a count was reached. Pagination does not provide a transactional snapshot, so records may change while you traverse them.
Continue with the same SaaS + San Francisco query in the second-page example, then run the complete Python pagination loop. It includes deduplication, repeated-cursor protection, bounded retries, and explicit partial-output reporting.
For the stopping conditions and data-consistency guidance, follow Paginating through the Search API. The Python example above intentionally stops after its first page.
Recover when a step does not work
| Result | Next action |
|---|---|
| Autocomplete returns several plausible matches | Compare type, permalink, and description. Resolve the ambiguity before searching. |
HTTP 200 with entities: [] | No matches were returned for this request. Check the selected criteria; remove one predicate temporarily to diagnose, then restore the intended query. Do not silently broaden a user's request. |
| HTTP 200 with a missing field | Check spelling and the schema for your package. Preserve missing values; do not replace them with zero or false. |
| 400 or a filter/validation error | Read the error message. Check JSON syntax, field, operator, and value type against your endpoint reference before retrying. |
| 401 or 403 | Check the provisioned key, X-cb-user-key header, and response message. Confirm package access with your account team if needed. |
| 404 during lookup | Check the organizations collection and returned organization UUID. Re-run the search if the record may have changed. |
| 429, 5xx, or a timeout | Follow bounded retry guidance. Honor Retry-After when provided. |
| Missing UUID, unexpected response shape, or mismatched lookup UUID | Stop the dependent lookup or discard the mismatched result. Inspect the response rather than guessing an identifier. |
When requesting help, share the endpoint, status, and sanitized request/error details. Do not include your key.
Use this workflow in an automated client
- Resolve each intended criterion to the correct identifier; ask for clarification when matches remain ambiguous.
- Use exact fields and supported operators from the package reference. Keep returned fields separate from filter predicates.
- Validate the HTTP result,
entitiesarray, and selected organization UUID before a dependent lookup. - Match the lookup UUID to the selected result and preserve missing optional data.
- Report the query and scope: this walkthrough reads one page and enriches one organization. Do not describe it as exhaustive or treat default result order as a recommendation.
- If the task requires all available results, use the canonical pagination workflow and account for changes during traversal.
Next steps
Updated 15 minutes ago

