mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-01 14:59:19 +08:00
feat(crunchbase): add Crunchbase Data API integration (#6875)
* feat(crunchbase): add Crunchbase Data API integration Covers the v4 Data API end to end: dedicated search and lookup operations for organizations, people, funding rounds, and acquisitions, plus generic collection-parameterized search and lookup reaching the remaining 39 collections, single-card paging, autocomplete, the deleted-entity feed, and fields metadata. Adds a crunchbase-errors extractor: the API answers failures with a bare JSON array, which no existing extractor reads, so an auth or predicate failure would have reported only its HTTP status. * fix(crunchbase): honor card paging limits and cursor exclusivity - Cap a card page at the documented 100-item maximum instead of Search's 1000, which the shared Limit field made easy to carry over - Always request the card's identifier so a narrowed cardFieldIds cannot return a full page with a null cursor and stall a paging loop - Reject the mutually-exclusive afterId/beforeId pair on the card and deleted-entity endpoints, not just on search - Report an unexpected card shape as empty rather than wrapping the envelope as a one-row page
This commit is contained in:
@@ -1489,15 +1489,15 @@ export function InstagramIcon(props: SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function CrunchbaseIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M21.6 0H2.4A2.41 2.41 0 0 0 0 2.4v19.2A2.41 2.41 0 0 0 2.4 24h19.2a2.41 2.41 0 0 0 2.4-2.4V2.4A2.41 2.41 0 0 0 21.6 0zM7.05 14.47A2.11 2.11 0 0 0 9.84 13.42h1.66a3.69 3.69 0 1 1 0-1.75H9.84a2.11 2.11 0 1 0-2.79 2.8zm11.35.845a3.55 3.55 0 0 1-1.06.63 3.68 3.68 0 0 1-3.39-.38v.38h-1.51V5.37h1.5v4.11a3.74 3.74 0 0 1 1.8-.63H16a3.67 3.67 0 0 1 2.39 6.46zm-.223-2.77a2.1 2.1 0 1 1-4.21 0 2.1 2.1 0 0 1 4.21 0z' />
|
||||
<svg {...props} viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198z'
|
||||
fill='#ffffff'
|
||||
/>
|
||||
<path
|
||||
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198zM9.396 19.286c1.411 0.646 3.078 0.021 3.724-1.391h2.214c-1.38 5.651-9.698 4.651-9.698-1.167 0-5.823 8.318-6.823 9.698-1.167h-2.214c-0.813-1.786-3.161-2.214-4.547-0.823-1.391 1.385-0.964 3.734 0.823 4.547zM24.521 20.411c-0.422 0.365-0.896 0.646-1.417 0.844-1.495 0.578-3.182 0.391-4.516-0.51v0.51h-2.016v-14.094h2v5.479c0.714-0.484 1.542-0.771 2.401-0.839h0.359c4.552-0.010 6.646 5.656 3.188 8.609zM24.224 16.724c0.031 1.573-1.234 2.87-2.807 2.87s-2.839-1.297-2.802-2.87c0.078-3.656 5.526-3.656 5.609 0z'
|
||||
fill='#0287d1'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
ContextDevIcon,
|
||||
ConvexIcon,
|
||||
CrowdStrikeIcon,
|
||||
CrunchbaseIcon,
|
||||
CursorIcon,
|
||||
DagsterIcon,
|
||||
DatabricksIcon,
|
||||
@@ -307,6 +308,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
context_dev: ContextDevIcon,
|
||||
convex: ConvexIcon,
|
||||
crowdstrike: CrowdStrikeIcon,
|
||||
crunchbase: CrunchbaseIcon,
|
||||
cursor: CursorIcon,
|
||||
cursor_v2: CursorIcon,
|
||||
dagster: DagsterIcon,
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
---
|
||||
title: Crunchbase
|
||||
description: Search and look up companies, people, funding rounds, and acquisitions
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="crunchbase"
|
||||
color="#0287D1"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Crunchbase](https://www.crunchbase.com/) is the private-market data platform companies use to find, research, and track businesses. Its Data API exposes the same graph the site is built on — organizations, people, funding rounds, acquisitions, IPOs, investments, jobs, events, and the relationships between them — through a single, uniform REST interface.
|
||||
|
||||
**Why Crunchbase?**
|
||||
- **One graph, not a scrape:** Companies, founders, investors, rounds, and deals are linked entities with stable UUIDs and permalinks, so a lookup resolves to the same record every time.
|
||||
- **Predicate search:** Every collection is queryable with the same filter grammar — twenty operators over any field the collection publishes — so an ideal-customer profile becomes a query rather than a script.
|
||||
- **Keyset pagination:** Results page forward by cursor instead of offset, so a full result set can be walked without the deep-page cost.
|
||||
- **Deletion feed:** A dedicated endpoint reports what Crunchbase removed, so a mirrored copy can be pruned in step with the source rather than drifting.
|
||||
|
||||
**Using Crunchbase in Sim**
|
||||
|
||||
Sim's Crunchbase integration covers the Data API end to end with an API key. Four collections most workflows reach for — organizations, people, funding rounds, and acquisitions — get dedicated search and lookup operations with sensible default field sets. The generic **Search Any Collection** and **Get Any Entity** operations reach the remaining 39 collections, including funds, investments, IPOs, jobs, press references, layoffs, insights, and predictions.
|
||||
|
||||
**Key benefits of using Crunchbase in Sim:**
|
||||
- **Account enrichment:** Resolve a company name to its permalink with Autocomplete, then pull headcount, headquarters, categories, and founding date onto the record.
|
||||
- **Target list building:** Turn an ICP into search predicates, page the full result set with the returned cursor, and write the companies to a table.
|
||||
- **Funding and deal monitoring:** Watch rounds and acquisitions announced in a window and route the summary to Slack, email, or a table.
|
||||
- **Deep relationship traversal:** **Get Entity Card** pages a single related-entity card — an investor's portfolio, a company's founders, a round's investors — past the 100-item cap an inline card request stops at.
|
||||
- **Field discovery:** **Get Fields Metadata** lists exactly which fields each collection publishes, which is how a query gets grounded before it runs.
|
||||
|
||||
**Before you start**
|
||||
|
||||
Requests authenticate with the `X-cb-user-key` header, and the API is rate limited to 200 calls per minute. Crunchbase sells the API in packages — Firmographic, Core Financials, Advanced Financials, Insights Only, and Predictions & Insights — and **which collections and fields answer depends on the package your key is licensed for**. The default field sets in this integration are drawn from the narrowest package that publishes each collection, so they resolve on the widest range of licenses; a request for a collection or field outside your license returns an error from Crunchbase rather than partial data.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrates the Crunchbase Data API into the workflow. Search organizations, people, funding rounds, and acquisitions with filter predicates, reach the other 39 collections through the generic search and lookup operations, page a single related-entity card past its 100-item cap, autocomplete names into identifiers, follow the deleted-entity feed, and list the fields each collection publishes. Which collections and fields resolve depends on your Crunchbase license.
|
||||
|
||||
|
||||
|
||||
## Actions
|
||||
|
||||
### Crunchbase Search Organizations
|
||||
|
||||
Search Crunchbase companies, investors, and schools with filter predicates on funding, headcount, location, category, and rank.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `query` | json | Yes | Filter predicates, combined with AND. Array of \{type:"predicate", field_id, operator_id, values\}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: \[\{"type":"predicate","field_id":"categories","operator_id":"includes","values":\["biotechnology"\]\}\] |
|
||||
| `fieldIds` | json | No | Organization fields to return as columns, e.g. \["identifier","name","founded_on","categories"\]. Defaults to identifier, name, short_description, website_url, linkedin, location_identifiers, categories, founded_on, num_employees_enum, operating_status, rank_org, permalink. |
|
||||
| `order` | json | No | Sort clauses, e.g. \[\{"field_id":"rank_org","sort":"asc","nulls":"last"\}\]. Sort is "asc" or "desc". |
|
||||
| `limit` | number | No | Rows to return, 1-1000 \(default 100\) |
|
||||
| `afterId` | string | No | UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId. |
|
||||
| `beforeId` | string | No | UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of organizations matching the query |
|
||||
| `entities` | json | Matching organizations as \[\{uuid, properties\}\], where properties holds the requested field_ids |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Organization
|
||||
|
||||
Look up a single Crunchbase organization by permalink or UUID, returning the requested fields and related cards.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `entityId` | string | Yes | Organization permalink \(e.g. "tesla-motors"\) or UUID |
|
||||
| `fieldIds` | json | No | Organization fields to return, e.g. \["identifier","name","founded_on","categories"\]. Defaults to identifier, name, short_description, website_url, linkedin, location_identifiers, categories, founded_on, num_employees_enum, operating_status, rank_org, permalink. |
|
||||
| `cardIds` | json | No | Related-entity cards to include, e.g. \["founders","headquarters_address"\]. Available on every license tier: child_organizations, child_ownerships, event_appearances, fields, founders, headquarters_address, parent_organization, parent_ownership. A card returns at most 100 items. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uuid` | string | Crunchbase UUID of the organization |
|
||||
| `name` | string | Organization name |
|
||||
| `permalink` | string | Crunchbase permalink of the organization |
|
||||
| `properties` | json | Requested organization fields, keyed by field_id |
|
||||
| `cards` | json | Requested related-entity cards, keyed by card_id |
|
||||
|
||||
### Crunchbase Search People
|
||||
|
||||
Search Crunchbase people — founders, executives, and investors — with filter predicates on job title, organization, location, and rank.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `query` | json | Yes | Filter predicates, combined with AND. Array of \{type:"predicate", field_id, operator_id, values\}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: \[\{"type":"predicate","field_id":"primary_job_title","operator_id":"contains","values":\["Founder"\]\}\] |
|
||||
| `fieldIds` | json | No | Person fields to return as columns, e.g. \["identifier","name","primary_job_title","primary_organization"\]. Defaults to identifier, name, first_name, last_name, primary_job_title, primary_organization, short_description, location_identifiers, linkedin, rank_person, permalink. |
|
||||
| `order` | json | No | Sort clauses, e.g. \[\{"field_id":"rank_person","sort":"asc","nulls":"last"\}\]. Sort is "asc" or "desc". |
|
||||
| `limit` | number | No | Rows to return, 1-1000 \(default 100\) |
|
||||
| `afterId` | string | No | UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId. |
|
||||
| `beforeId` | string | No | UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of people matching the query |
|
||||
| `entities` | json | Matching people as \[\{uuid, properties\}\], where properties holds the requested field_ids |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Person
|
||||
|
||||
Look up a single Crunchbase person by permalink or UUID, returning the requested fields and related cards.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `entityId` | string | Yes | Person permalink \(e.g. "elon-musk"\) or UUID |
|
||||
| `fieldIds` | json | No | Person fields to return, e.g. \["identifier","name","primary_job_title","primary_organization"\]. Defaults to identifier, name, first_name, last_name, primary_job_title, primary_organization, short_description, location_identifiers, linkedin, rank_person, permalink. |
|
||||
| `cardIds` | json | No | Related-entity cards to include, e.g. \["jobs","primary_organization"\]. Available: degrees, event_appearances, fields, founded_organizations, jobs, primary_job, primary_organization. A card returns at most 100 items. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uuid` | string | Crunchbase UUID of the person |
|
||||
| `name` | string | Full name of the person |
|
||||
| `permalink` | string | Crunchbase permalink of the person |
|
||||
| `properties` | json | Requested person fields, keyed by field_id |
|
||||
| `cards` | json | Requested related-entity cards, keyed by card_id |
|
||||
|
||||
### Crunchbase Search Funding Rounds
|
||||
|
||||
Search Crunchbase funding rounds with filter predicates on announced date, investment type, amount raised, and investors.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `query` | json | Yes | Filter predicates, combined with AND. Array of \{type:"predicate", field_id, operator_id, values\}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: \[\{"type":"predicate","field_id":"announced_on","operator_id":"gte","values":\["2026-01-01"\]\}\] |
|
||||
| `fieldIds` | json | No | Funding round fields to return as columns, e.g. \["identifier","announced_on","money_raised","investor_identifiers"\]. Defaults to identifier, announced_on, investment_type, investment_stage, money_raised, funded_organization_identifier, investor_identifiers, lead_investor_identifiers, num_investors, short_description, permalink. |
|
||||
| `order` | json | No | Sort clauses, e.g. \[\{"field_id":"announced_on","sort":"desc","nulls":"last"\}\]. Sort is "asc" or "desc". |
|
||||
| `limit` | number | No | Rows to return, 1-1000 \(default 100\) |
|
||||
| `afterId` | string | No | UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId. |
|
||||
| `beforeId` | string | No | UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of funding rounds matching the query |
|
||||
| `entities` | json | Matching funding rounds as \[\{uuid, properties\}\], where properties holds the requested field_ids |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Funding Round
|
||||
|
||||
Look up a single Crunchbase funding round by permalink or UUID, returning the requested fields and related cards.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `entityId` | string | Yes | Funding round permalink \(e.g. "tesla-motors-series-c--12345678"\) or UUID |
|
||||
| `fieldIds` | json | No | Funding round fields to return, e.g. \["identifier","announced_on","money_raised","investor_identifiers"\]. Defaults to identifier, announced_on, investment_type, investment_stage, money_raised, funded_organization_identifier, investor_identifiers, lead_investor_identifiers, num_investors, short_description, permalink. |
|
||||
| `cardIds` | json | No | Related-entity cards to include, e.g. \["investors","organization"\]. Available: fields, investments, investors, lead_investors, organization, partners, press_references. A card returns at most 100 items. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uuid` | string | Crunchbase UUID of the funding round |
|
||||
| `name` | string | Funding round name |
|
||||
| `permalink` | string | Crunchbase permalink of the funding round |
|
||||
| `properties` | json | Requested funding round fields, keyed by field_id |
|
||||
| `cards` | json | Requested related-entity cards, keyed by card_id |
|
||||
|
||||
### Crunchbase Search Acquisitions
|
||||
|
||||
Search Crunchbase acquisitions with filter predicates on announced date, price, acquisition type, and the companies involved.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `query` | json | Yes | Filter predicates, combined with AND. Array of \{type:"predicate", field_id, operator_id, values\}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: \[\{"type":"predicate","field_id":"announced_on","operator_id":"gte","values":\["2026-01-01"\]\}\] |
|
||||
| `fieldIds` | json | No | Acquisition fields to return as columns, e.g. \["identifier","acquiree_identifier","acquirer_identifier","price"\]. Defaults to identifier, acquiree_identifier, acquirer_identifier, announced_on, completed_on, price, acquisition_type, status, terms, short_description, permalink. |
|
||||
| `order` | json | No | Sort clauses, e.g. \[\{"field_id":"announced_on","sort":"desc","nulls":"last"\}\]. Sort is "asc" or "desc". |
|
||||
| `limit` | number | No | Rows to return, 1-1000 \(default 100\) |
|
||||
| `afterId` | string | No | UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId. |
|
||||
| `beforeId` | string | No | UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of acquisitions matching the query |
|
||||
| `entities` | json | Matching acquisitions as \[\{uuid, properties\}\], where properties holds the requested field_ids |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Acquisition
|
||||
|
||||
Look up a single Crunchbase acquisition by permalink or UUID, returning the requested fields and related cards.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `entityId` | string | Yes | Acquisition permalink or UUID |
|
||||
| `fieldIds` | json | No | Acquisition fields to return, e.g. \["identifier","acquiree_identifier","acquirer_identifier","price"\]. Defaults to identifier, acquiree_identifier, acquirer_identifier, announced_on, completed_on, price, acquisition_type, status, terms, short_description, permalink. |
|
||||
| `cardIds` | json | No | Related-entity cards to include, e.g. \["acquiree_organization","acquirer_organization"\]. Available: acquiree_organization, acquirer_organization, fields, press_references. A card returns at most 100 items. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uuid` | string | Crunchbase UUID of the acquisition |
|
||||
| `name` | string | Acquisition name |
|
||||
| `permalink` | string | Crunchbase permalink of the acquisition |
|
||||
| `properties` | json | Requested acquisition fields, keyed by field_id |
|
||||
| `cards` | json | Requested related-entity cards, keyed by card_id |
|
||||
|
||||
### Crunchbase Search Entities
|
||||
|
||||
Search any Crunchbase collection — events, jobs, ipos, funds, investments, press references, layoffs, insights, predictions, and more — with filter predicates.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `collection` | string | Yes | Collection to search. One of: acquisition_predictions, acquisitions, addresses, awards, categories, category_groups, closure_predictions, current_valuation_estimates, degrees, diversity_spotlights, event_appearances, events, funding_predictions, funding_rounds, funds, growth_insights, growth_predictions, investments, investor_insights, investor_matches, ipo_predictions, ipos, jobs, key_employee_changes, layoff_predictions, layoffs, legal_proceedings, locations, market_insight_reasons, market_insights, micro_categories, org_similarities, organizations, ownerships, partnership_announcements, people, press_references, principals, product_launches, product_similarities, products, remain_private_predictions, research_insights. |
|
||||
| `query` | json | Yes | Filter predicates, combined with AND. Array of \{type:"predicate", field_id, operator_id, values\}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. |
|
||||
| `fieldIds` | json | Yes | Fields to return as columns for the chosen collection, e.g. \["identifier","short_description"\]. Required — the valid ids differ per collection; list them with the Get Fields Metadata operation. |
|
||||
| `order` | json | No | Sort clauses, e.g. \[\{"field_id":"updated_at","sort":"desc","nulls":"last"\}\]. Sort is "asc" or "desc". |
|
||||
| `limit` | number | No | Rows to return, 1-1000 \(default 100\) |
|
||||
| `afterId` | string | No | UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId. |
|
||||
| `beforeId` | string | No | UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of entities matching the query |
|
||||
| `entities` | json | Matching entities as \[\{uuid, properties\}\], where properties holds the requested field_ids |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Entity
|
||||
|
||||
Look up a single entity in any Crunchbase collection — events, jobs, ipos, funds, investments, press references, insights, predictions, and more — by permalink or UUID.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `collection` | string | Yes | Collection the entity belongs to. One of: acquisition_predictions, acquisitions, addresses, awards, categories, category_groups, closure_predictions, current_valuation_estimates, degrees, diversity_spotlights, event_appearances, events, funding_predictions, funding_rounds, funds, growth_insights, growth_predictions, investments, investor_insights, investor_matches, ipo_predictions, ipos, jobs, key_employee_changes, layoff_predictions, layoffs, legal_proceedings, locations, market_insight_reasons, market_insights, micro_categories, org_similarities, organizations, ownerships, partnership_announcements, people, press_references, principals, product_launches, product_similarities, products, remain_private_predictions, research_insights. |
|
||||
| `entityId` | string | Yes | Entity permalink or UUID |
|
||||
| `fieldIds` | json | No | Fields to return for the chosen collection, e.g. \["identifier","short_description"\]. Leave empty to accept the default projection the API returns; list the valid ids with the Get Fields Metadata operation. |
|
||||
| `cardIds` | json | No | Related-entity cards to include. The valid ids differ per collection, and a card returns at most 100 items — use the Get Entity Card operation to page past that. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uuid` | string | Crunchbase UUID of the entity |
|
||||
| `name` | string | Name of the entity |
|
||||
| `permalink` | string | Crunchbase permalink of the entity |
|
||||
| `properties` | json | Requested entity fields, keyed by field_id |
|
||||
| `cards` | json | Requested related-entity cards, keyed by card_id |
|
||||
|
||||
### Crunchbase Get Entity Card
|
||||
|
||||
Page through one related-entity card of a Crunchbase entity — an investor's investments, a company's founders, a round's investors — past the 100-item cap an inline card request returns.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `collection` | string | Yes | Collection the entity belongs to. One of: acquisitions, addresses, categories, category_groups, degrees, event_appearances, events, funding_rounds, funds, investments, ipos, jobs, market_insights, micro_categories, organizations, ownerships, people. |
|
||||
| `entityId` | string | Yes | Entity permalink or UUID |
|
||||
| `cardId` | string | Yes | Card to page through, e.g. "participated_investments" on a person, "founders" on an organization, or "investors" on a funding round. Valid ids differ per collection. |
|
||||
| `cardFieldIds` | json | No | Fields to return on each card item, e.g. \["identifier","announced_on","money_raised"\]. The identifier is always requested alongside these, because the next-page cursor is read from it. |
|
||||
| `cardOrder` | string | No | Sort expression for the card, e.g. "funding_round_money_raised desc" |
|
||||
| `limit` | number | No | Card items to return per page, 1-100 |
|
||||
| `afterId` | string | No | UUID of the last card item on the current page, to fetch the next page |
|
||||
| `beforeId` | string | No | UUID of the first card item on the current page, to fetch the previous page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | Card items for this page, each holding the requested card_field_ids |
|
||||
| `properties` | json | Properties of the parent entity returned alongside the card |
|
||||
| `nextAfterId` | string | UUID of the last card item, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Autocomplete
|
||||
|
||||
Suggest Crunchbase entities matching a typed query, returning the permalinks and UUIDs the lookup and search operations take.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `query` | string | Yes | Text to autocomplete against, e.g. "airbnb" |
|
||||
| `collectionIds` | json | No | Collections to search, e.g. \["organizations","people"\]. One or more of: addresses, categories, category_groups, degrees, diversity_spotlights, event_appearances, events, ipos, jobs, locations, organizations, ownerships, people, principals. Defaults to every collection. |
|
||||
| `limit` | number | No | Suggestions to return, max 25 \(default 10\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `entities` | json | Suggestions as \[\{identifier: \{uuid, value, permalink, image_id, entity_def_id\}, facet_ids, short_description\}\] |
|
||||
|
||||
### Crunchbase List Deleted Entities
|
||||
|
||||
List entities Crunchbase has deleted, so a mirrored copy can be pruned in step with the source.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `collection` | string | No | Restrict the feed to a single collection: categories, event_appearances, events, ipos, jobs, locations, organizations, ownerships, or people. Leave empty to read the feed across collections. |
|
||||
| `collectionIds` | json | No | Collections to include when reading the cross-collection feed, e.g. \["organizations","people"\]. Ignored when a single collection is set. |
|
||||
| `deletedAtOrder` | string | No | Order by deletion time: "asc" \(default\) or "desc" |
|
||||
| `limit` | number | No | Rows to return per page |
|
||||
| `afterId` | string | No | UUID of the last row on the current page, to fetch the next page |
|
||||
| `beforeId` | string | No | UUID of the first row on the current page, to fetch the previous page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `entities` | json | Deleted entities as \[\{deleted_at, identifier: \{uuid, value, permalink, entity_def_id\}\}\] |
|
||||
| `nextAfterId` | string | UUID of the last row, to pass as afterId for the next page |
|
||||
|
||||
### Crunchbase Get Fields Metadata
|
||||
|
||||
List the field ids, types, and descriptions each Crunchbase collection publishes, which is how the field_ids and query predicates of the other operations are discovered.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Crunchbase API key, sent as the X-cb-user-key header |
|
||||
| `collectionIds` | json | No | Collections to describe, e.g. \["organizations","people"\]. One or more of: addresses, categories, category_groups, degrees, diversity_spotlights, event_appearances, events, ipos, jobs, locations, organizations, ownerships, people, principals. Defaults to every collection. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `csv` | string | Field metadata as CSV, one row per field with its collection, id, type, and description |
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"context_dev",
|
||||
"convex",
|
||||
"crowdstrike",
|
||||
"crunchbase",
|
||||
"cursor",
|
||||
"dagster",
|
||||
"databricks",
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CrunchbaseBlock } from '@/blocks/blocks/crunchbase'
|
||||
|
||||
/**
|
||||
* Every assertion here runs against `{ ...inputs, ...buildParams(inputs) }`, the
|
||||
* shape the generic tool handler actually forwards. A key the mapper omits is
|
||||
* *not* dropped by that merge — the raw subBlock value survives — so asserting
|
||||
* on the mapper's return alone would prove nothing about what the tool receives.
|
||||
*/
|
||||
describe('CrunchbaseBlock', () => {
|
||||
const buildParams = CrunchbaseBlock.tools.config!.params!
|
||||
const selectTool = CrunchbaseBlock.tools.config!.tool!
|
||||
|
||||
const resolve = (inputs: Record<string, unknown>) => ({ ...inputs, ...buildParams(inputs) })
|
||||
|
||||
const operationIds =
|
||||
CrunchbaseBlock.subBlocks
|
||||
.find((subBlock) => subBlock.id === 'operation')
|
||||
?.options?.map((option) => (option as { id: string }).id) ?? []
|
||||
|
||||
it('maps every dropdown operation onto a registered tool', () => {
|
||||
expect(operationIds).toHaveLength(14)
|
||||
expect(new Set(operationIds.map((id) => selectTool({ operation: id })))).toEqual(
|
||||
new Set(CrunchbaseBlock.tools.access)
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects an operation the dropdown does not offer', () => {
|
||||
expect(() => selectTool({ operation: 'search_unicorns' })).toThrow(
|
||||
/Invalid Crunchbase operation/
|
||||
)
|
||||
})
|
||||
|
||||
it('gives every subblock a unique id', () => {
|
||||
const ids = CrunchbaseBlock.subBlocks.map((subBlock) => subBlock.id)
|
||||
expect(ids).toHaveLength(new Set(ids).size)
|
||||
})
|
||||
|
||||
it('shows a subblock for every operation that owns it', () => {
|
||||
const conditionsFor = (id: string) => {
|
||||
const condition = CrunchbaseBlock.subBlocks.find((subBlock) => subBlock.id === id)?.condition
|
||||
const value = (condition as { value?: unknown } | undefined)?.value
|
||||
return new Set(Array.isArray(value) ? value.map(String) : [String(value)])
|
||||
}
|
||||
|
||||
expect(conditionsFor('searchQuery')).toEqual(
|
||||
new Set([
|
||||
'search_organizations',
|
||||
'search_people',
|
||||
'search_funding_rounds',
|
||||
'search_acquisitions',
|
||||
'search_entities',
|
||||
])
|
||||
)
|
||||
expect(conditionsFor('entityId')).toEqual(
|
||||
new Set([
|
||||
'get_organization',
|
||||
'get_person',
|
||||
'get_funding_round',
|
||||
'get_acquisition',
|
||||
'get_entity',
|
||||
'get_entity_card',
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it('never hides a required field behind advanced mode', () => {
|
||||
const advancedRequired = CrunchbaseBlock.subBlocks
|
||||
.filter((subBlock) => subBlock.mode === 'advanced' && subBlock.required)
|
||||
.map((subBlock) => subBlock.id)
|
||||
|
||||
expect(advancedRequired).toEqual([])
|
||||
})
|
||||
|
||||
/*
|
||||
* The mapper's whole job is dropping keys that belong to another operation.
|
||||
* `shouldSerializeSubBlock` short-circuits on `mode: 'advanced'` before it
|
||||
* evaluates a condition, so a hidden advanced field still reaches `inputs` —
|
||||
* only an explicit `undefined` removes it from what goes out on the wire.
|
||||
*/
|
||||
it('drops a previous operation’s leftovers when the operation changes', () => {
|
||||
const params = resolve({
|
||||
operation: 'autocomplete',
|
||||
apiKey: 'key',
|
||||
autocompleteQuery: 'airbnb',
|
||||
searchQuery: '[{"type":"predicate","field_id":"categories"}]',
|
||||
searchFieldIds: '["identifier"]',
|
||||
entityId: 'tesla-motors',
|
||||
fieldIds: '["identifier","name"]',
|
||||
cardIds: '["founders"]',
|
||||
order: '[{"field_id":"rank_org","sort":"asc"}]',
|
||||
afterId: 'stale-cursor',
|
||||
collection: 'organizations',
|
||||
})
|
||||
|
||||
expect(params.query).toBe('airbnb')
|
||||
expect(params.entityId).toBeUndefined()
|
||||
expect(params.fieldIds).toBeUndefined()
|
||||
expect(params.cardIds).toBeUndefined()
|
||||
expect(params.order).toBeUndefined()
|
||||
expect(params.afterId).toBeUndefined()
|
||||
expect(params.collection).toBeUndefined()
|
||||
expect(params.searchQuery).toBeUndefined()
|
||||
expect(params.autocompleteQuery).toBeUndefined()
|
||||
})
|
||||
|
||||
it('routes the predicate query to a search and the text query to autocomplete', () => {
|
||||
const search = resolve({
|
||||
operation: 'search_organizations',
|
||||
apiKey: 'key',
|
||||
searchQuery: '[{"type":"predicate","field_id":"categories","operator_id":"includes"}]',
|
||||
autocompleteQuery: 'airbnb',
|
||||
})
|
||||
expect(search.query).toBe(
|
||||
'[{"type":"predicate","field_id":"categories","operator_id":"includes"}]'
|
||||
)
|
||||
|
||||
const autocomplete = resolve({
|
||||
operation: 'autocomplete',
|
||||
apiKey: 'key',
|
||||
searchQuery: '[{"type":"predicate","field_id":"categories","operator_id":"includes"}]',
|
||||
autocompleteQuery: 'airbnb',
|
||||
})
|
||||
expect(autocomplete.query).toBe('airbnb')
|
||||
})
|
||||
|
||||
it('feeds the generic search its own required field list', () => {
|
||||
const generic = resolve({
|
||||
operation: 'search_entities',
|
||||
apiKey: 'key',
|
||||
collection: 'events',
|
||||
searchQuery: '[]',
|
||||
searchFieldIds: '["identifier","short_description"]',
|
||||
fieldIds: '["identifier","name"]',
|
||||
})
|
||||
expect(generic.fieldIds).toBe('["identifier","short_description"]')
|
||||
expect(generic.searchFieldIds).toBeUndefined()
|
||||
|
||||
const specific = resolve({
|
||||
operation: 'search_organizations',
|
||||
apiKey: 'key',
|
||||
searchQuery: '[]',
|
||||
searchFieldIds: '["identifier","short_description"]',
|
||||
fieldIds: '["identifier","name"]',
|
||||
})
|
||||
expect(specific.fieldIds).toBe('["identifier","name"]')
|
||||
})
|
||||
|
||||
it('picks the collection each operation actually asks for', () => {
|
||||
const card = resolve({
|
||||
operation: 'get_entity_card',
|
||||
apiKey: 'key',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
cardCollection: 'organizations',
|
||||
collection: 'events',
|
||||
deletedCollection: 'people',
|
||||
})
|
||||
expect(card.collection).toBe('organizations')
|
||||
expect(card.cardCollection).toBeUndefined()
|
||||
|
||||
const deleted = resolve({
|
||||
operation: 'list_deleted_entities',
|
||||
apiKey: 'key',
|
||||
cardCollection: 'organizations',
|
||||
collection: 'events',
|
||||
deletedCollection: 'people',
|
||||
})
|
||||
expect(deleted.collection).toBe('people')
|
||||
expect(deleted.deletedCollection).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,637 @@
|
||||
import { Building, ListChecks, Search, Sprout, Trash, Users } from '@sim/emcn/icons'
|
||||
import { CrunchbaseIcon } from '@/components/icons'
|
||||
import type { BlockConfig, BlockMeta } from '@/blocks/types'
|
||||
import { AuthMode, IntegrationType } from '@/blocks/types'
|
||||
import type { CrunchbaseResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
CRUNCHBASE_CARD_COLLECTIONS,
|
||||
CRUNCHBASE_COLLECTIONS,
|
||||
CRUNCHBASE_DELETED_COLLECTIONS,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
|
||||
/** Operations that POST to `/data/searches/{collection}`. */
|
||||
const SEARCH_OPERATIONS = [
|
||||
'search_organizations',
|
||||
'search_people',
|
||||
'search_funding_rounds',
|
||||
'search_acquisitions',
|
||||
'search_entities',
|
||||
] as const
|
||||
|
||||
/** Operations that GET `/data/entities/{collection}/{entity_id}`. */
|
||||
const LOOKUP_OPERATIONS = [
|
||||
'get_organization',
|
||||
'get_person',
|
||||
'get_funding_round',
|
||||
'get_acquisition',
|
||||
'get_entity',
|
||||
] as const
|
||||
|
||||
/** Operations that take a collection dropdown rather than an implied collection. */
|
||||
const COLLECTION_OPERATIONS = ['search_entities', 'get_entity'] as const
|
||||
|
||||
/** Operations whose field list falls back to a verified per-collection default. */
|
||||
const DEFAULTED_FIELD_ID_OPERATIONS = [...SEARCH_OPERATIONS, ...LOOKUP_OPERATIONS].filter(
|
||||
(operation) => operation !== 'search_entities'
|
||||
)
|
||||
|
||||
/** Operations whose endpoint accepts a `limit`. */
|
||||
const LIMITED_OPERATIONS = [
|
||||
...SEARCH_OPERATIONS,
|
||||
'autocomplete',
|
||||
'get_entity_card',
|
||||
'list_deleted_entities',
|
||||
]
|
||||
|
||||
/** Operations paged with the keyset `after_id` / `before_id` cursors. */
|
||||
const CURSOR_OPERATIONS = [...SEARCH_OPERATIONS, 'get_entity_card', 'list_deleted_entities']
|
||||
|
||||
/** Turns a collection id into the dropdown label a reader expects. */
|
||||
function toCollectionOption(id: string) {
|
||||
return {
|
||||
label: id
|
||||
.split('_')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' '),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
const COLLECTION_OPTIONS = CRUNCHBASE_COLLECTIONS.map(toCollectionOption)
|
||||
const CARD_COLLECTION_OPTIONS = CRUNCHBASE_CARD_COLLECTIONS.map(toCollectionOption)
|
||||
const DELETED_COLLECTION_OPTIONS = CRUNCHBASE_DELETED_COLLECTIONS.map(toCollectionOption)
|
||||
|
||||
const QUERY_WAND_PROMPT = `Generate a Crunchbase Search API "query" array.
|
||||
|
||||
Each element is a predicate object: {"type":"predicate","field_id":"...","operator_id":"...","values":[...]}.
|
||||
Predicates are combined with AND only; at most 25 are allowed, each with at most 200 values.
|
||||
Valid operator_id values: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes.
|
||||
"between" takes exactly two values. "blank" takes "true" or "false".
|
||||
Use field_ids belonging to the collection being searched — organizations support categories, location_identifiers, founded_on, num_employees_enum, operating_status and rank_org; people support primary_job_title and primary_organization; funding rounds support announced_on, investment_type and money_raised; acquisitions support announced_on, price and acquisition_type.
|
||||
|
||||
Return ONLY the JSON array.`
|
||||
|
||||
export const CrunchbaseBlock: BlockConfig<CrunchbaseResponse> = {
|
||||
type: 'crunchbase',
|
||||
name: 'Crunchbase',
|
||||
description: 'Search and look up companies, people, funding rounds, and acquisitions',
|
||||
longDescription:
|
||||
'Integrates the Crunchbase Data API into the workflow. Search organizations, people, funding rounds, and acquisitions with filter predicates, reach the other 39 collections through the generic search and lookup operations, page a single related-entity card past its 100-item cap, autocomplete names into identifiers, follow the deleted-entity feed, and list the fields each collection publishes. Which collections and fields resolve depends on your Crunchbase license.',
|
||||
docsLink: 'https://docs.sim.ai/integrations/crunchbase',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Sales,
|
||||
bgColor: '#0287D1',
|
||||
icon: CrunchbaseIcon,
|
||||
authMode: AuthMode.ApiKey,
|
||||
canvasPresentation: {
|
||||
defaultTitle: 'Crunchbase',
|
||||
sentences: {
|
||||
byOperation: {
|
||||
search_organizations: [
|
||||
'Search organizations',
|
||||
{ text: 'matching', field: 'searchQuery', core: true },
|
||||
],
|
||||
search_people: ['Search people', { text: 'matching', field: 'searchQuery', core: true }],
|
||||
search_funding_rounds: [
|
||||
'Search funding rounds',
|
||||
{ text: 'matching', field: 'searchQuery', core: true },
|
||||
],
|
||||
search_acquisitions: [
|
||||
'Search acquisitions',
|
||||
{ text: 'matching', field: 'searchQuery', core: true },
|
||||
],
|
||||
search_entities: [
|
||||
{ text: 'Search', field: 'collection', core: true },
|
||||
{ text: 'matching', field: 'searchQuery', core: true },
|
||||
],
|
||||
get_organization: [{ text: 'Look up organization', field: 'entityId', core: true }],
|
||||
get_person: [{ text: 'Look up person', field: 'entityId', core: true }],
|
||||
get_funding_round: [{ text: 'Look up funding round', field: 'entityId', core: true }],
|
||||
get_acquisition: [{ text: 'Look up acquisition', field: 'entityId', core: true }],
|
||||
get_entity: [
|
||||
{ text: 'Look up', field: 'entityId', core: true },
|
||||
{ text: 'in', field: 'collection', core: true },
|
||||
],
|
||||
get_entity_card: [
|
||||
{ text: 'Read', field: 'cardId', core: true },
|
||||
{ text: 'of', field: 'entityId', core: true },
|
||||
],
|
||||
autocomplete: [
|
||||
{ text: 'Suggest entities matching', field: 'autocompleteQuery', core: true },
|
||||
],
|
||||
list_deleted_entities: [
|
||||
'List deleted entities',
|
||||
{ text: 'from', field: 'deletedCollection' },
|
||||
],
|
||||
get_fields_metadata: ['List the fields each collection publishes'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Search Organizations', id: 'search_organizations' },
|
||||
{ label: 'Get Organization', id: 'get_organization' },
|
||||
{ label: 'Search People', id: 'search_people' },
|
||||
{ label: 'Get Person', id: 'get_person' },
|
||||
{ label: 'Search Funding Rounds', id: 'search_funding_rounds' },
|
||||
{ label: 'Get Funding Round', id: 'get_funding_round' },
|
||||
{ label: 'Search Acquisitions', id: 'search_acquisitions' },
|
||||
{ label: 'Get Acquisition', id: 'get_acquisition' },
|
||||
{ label: 'Search Any Collection', id: 'search_entities' },
|
||||
{ label: 'Get Any Entity', id: 'get_entity' },
|
||||
{ label: 'Get Entity Card', id: 'get_entity_card' },
|
||||
{ label: 'Autocomplete', id: 'autocomplete' },
|
||||
{ label: 'List Deleted Entities', id: 'list_deleted_entities' },
|
||||
{ label: 'Get Fields Metadata', id: 'get_fields_metadata' },
|
||||
],
|
||||
value: () => 'search_organizations',
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'Crunchbase API Key',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter your Crunchbase API key',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'collection',
|
||||
title: 'Collection',
|
||||
type: 'dropdown',
|
||||
options: COLLECTION_OPTIONS,
|
||||
condition: { field: 'operation', value: [...COLLECTION_OPERATIONS] },
|
||||
required: { field: 'operation', value: [...COLLECTION_OPERATIONS] },
|
||||
},
|
||||
{
|
||||
id: 'searchQuery',
|
||||
title: 'Query',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder:
|
||||
'[{"type":"predicate","field_id":"categories","operator_id":"includes","values":["biotechnology"]}]',
|
||||
condition: { field: 'operation', value: [...SEARCH_OPERATIONS] },
|
||||
required: { field: 'operation', value: [...SEARCH_OPERATIONS] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: QUERY_WAND_PROMPT,
|
||||
generationType: 'json-array',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'entityId',
|
||||
title: 'Entity ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Permalink or UUID (e.g. tesla-motors)',
|
||||
condition: { field: 'operation', value: [...LOOKUP_OPERATIONS, 'get_entity_card'] },
|
||||
required: { field: 'operation', value: [...LOOKUP_OPERATIONS, 'get_entity_card'] },
|
||||
},
|
||||
{
|
||||
id: 'autocompleteQuery',
|
||||
title: 'Search Text',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. airbnb',
|
||||
condition: { field: 'operation', value: 'autocomplete' },
|
||||
required: { field: 'operation', value: 'autocomplete' },
|
||||
},
|
||||
{
|
||||
id: 'cardCollection',
|
||||
title: 'Collection',
|
||||
type: 'dropdown',
|
||||
options: CARD_COLLECTION_OPTIONS,
|
||||
condition: { field: 'operation', value: 'get_entity_card' },
|
||||
required: { field: 'operation', value: 'get_entity_card' },
|
||||
},
|
||||
{
|
||||
id: 'cardId',
|
||||
title: 'Card ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. founders, investors, participated_investments',
|
||||
condition: { field: 'operation', value: 'get_entity_card' },
|
||||
required: { field: 'operation', value: 'get_entity_card' },
|
||||
},
|
||||
{
|
||||
id: 'deletedCollection',
|
||||
title: 'Collection',
|
||||
type: 'dropdown',
|
||||
options: DELETED_COLLECTION_OPTIONS,
|
||||
placeholder: 'All collections',
|
||||
condition: { field: 'operation', value: 'list_deleted_entities' },
|
||||
},
|
||||
{
|
||||
id: 'searchFieldIds',
|
||||
title: 'Field IDs',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '["identifier","short_description","updated_at"]',
|
||||
condition: { field: 'operation', value: 'search_entities' },
|
||||
required: { field: 'operation', value: 'search_entities' },
|
||||
},
|
||||
{
|
||||
id: 'fieldIds',
|
||||
title: 'Field IDs',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
mode: 'advanced',
|
||||
placeholder: '["identifier","name","founded_on","categories"]',
|
||||
condition: { field: 'operation', value: DEFAULTED_FIELD_ID_OPERATIONS },
|
||||
},
|
||||
{
|
||||
id: 'cardIds',
|
||||
title: 'Card IDs',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
mode: 'advanced',
|
||||
placeholder: '["founders","headquarters_address"]',
|
||||
condition: { field: 'operation', value: [...LOOKUP_OPERATIONS] },
|
||||
},
|
||||
{
|
||||
id: 'cardFieldIds',
|
||||
title: 'Card Field IDs',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
mode: 'advanced',
|
||||
placeholder: '["identifier","announced_on","money_raised"]',
|
||||
condition: { field: 'operation', value: 'get_entity_card' },
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
title: 'Order',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
mode: 'advanced',
|
||||
placeholder: '[{"field_id":"rank_org","sort":"asc","nulls":"last"}]',
|
||||
condition: { field: 'operation', value: [...SEARCH_OPERATIONS] },
|
||||
},
|
||||
{
|
||||
id: 'cardOrder',
|
||||
title: 'Order',
|
||||
type: 'short-input',
|
||||
mode: 'advanced',
|
||||
placeholder: 'e.g. funding_round_money_raised desc',
|
||||
condition: { field: 'operation', value: 'get_entity_card' },
|
||||
},
|
||||
{
|
||||
id: 'deletedAtOrder',
|
||||
title: 'Deleted At Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Oldest first', id: 'asc' },
|
||||
{ label: 'Newest first', id: 'desc' },
|
||||
],
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'list_deleted_entities' },
|
||||
},
|
||||
{
|
||||
id: 'collectionIds',
|
||||
title: 'Collection IDs',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
mode: 'advanced',
|
||||
placeholder: '["organizations","people"]',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['autocomplete', 'list_deleted_entities', 'get_fields_metadata'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
mode: 'advanced',
|
||||
placeholder: 'Search: 1-1000 (default 100). Card: 1-100. Autocomplete: 1-25 (default 10)',
|
||||
condition: { field: 'operation', value: LIMITED_OPERATIONS },
|
||||
},
|
||||
{
|
||||
id: 'afterId',
|
||||
title: 'After ID',
|
||||
type: 'short-input',
|
||||
mode: 'advanced',
|
||||
placeholder: 'UUID of the last row on the current page',
|
||||
condition: { field: 'operation', value: CURSOR_OPERATIONS },
|
||||
},
|
||||
{
|
||||
id: 'beforeId',
|
||||
title: 'Before ID',
|
||||
type: 'short-input',
|
||||
mode: 'advanced',
|
||||
placeholder: 'UUID of the first row on the current page',
|
||||
condition: { field: 'operation', value: CURSOR_OPERATIONS },
|
||||
},
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: [
|
||||
'crunchbase_search_organizations',
|
||||
'crunchbase_get_organization',
|
||||
'crunchbase_search_people',
|
||||
'crunchbase_get_person',
|
||||
'crunchbase_search_funding_rounds',
|
||||
'crunchbase_get_funding_round',
|
||||
'crunchbase_search_acquisitions',
|
||||
'crunchbase_get_acquisition',
|
||||
'crunchbase_search_entities',
|
||||
'crunchbase_get_entity',
|
||||
'crunchbase_get_entity_card',
|
||||
'crunchbase_autocomplete',
|
||||
'crunchbase_list_deleted_entities',
|
||||
'crunchbase_get_fields_metadata',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'search_organizations':
|
||||
return 'crunchbase_search_organizations'
|
||||
case 'get_organization':
|
||||
return 'crunchbase_get_organization'
|
||||
case 'search_people':
|
||||
return 'crunchbase_search_people'
|
||||
case 'get_person':
|
||||
return 'crunchbase_get_person'
|
||||
case 'search_funding_rounds':
|
||||
return 'crunchbase_search_funding_rounds'
|
||||
case 'get_funding_round':
|
||||
return 'crunchbase_get_funding_round'
|
||||
case 'search_acquisitions':
|
||||
return 'crunchbase_search_acquisitions'
|
||||
case 'get_acquisition':
|
||||
return 'crunchbase_get_acquisition'
|
||||
case 'search_entities':
|
||||
return 'crunchbase_search_entities'
|
||||
case 'get_entity':
|
||||
return 'crunchbase_get_entity'
|
||||
case 'get_entity_card':
|
||||
return 'crunchbase_get_entity_card'
|
||||
case 'autocomplete':
|
||||
return 'crunchbase_autocomplete'
|
||||
case 'list_deleted_entities':
|
||||
return 'crunchbase_list_deleted_entities'
|
||||
case 'get_fields_metadata':
|
||||
return 'crunchbase_get_fields_metadata'
|
||||
default:
|
||||
throw new Error(`Invalid Crunchbase operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Every key the block can send is assigned unconditionally.
|
||||
*
|
||||
* The executor merges the raw subblock state underneath this result, so
|
||||
* omitting a key leaves the previous operation's value on the wire — an
|
||||
* advanced field is serialized on non-emptiness alone, even while the UI
|
||||
* hides it. `undefined` is what actually drops one.
|
||||
*/
|
||||
params: (params) => {
|
||||
const operation = String(params.operation ?? '')
|
||||
const isSearch = (SEARCH_OPERATIONS as readonly string[]).includes(operation)
|
||||
const isLookup = (LOOKUP_OPERATIONS as readonly string[]).includes(operation)
|
||||
const isCollectionScoped = (COLLECTION_OPERATIONS as readonly string[]).includes(operation)
|
||||
const isCard = operation === 'get_entity_card'
|
||||
const isAutocomplete = operation === 'autocomplete'
|
||||
const isDeleted = operation === 'list_deleted_entities'
|
||||
const isMetadata = operation === 'get_fields_metadata'
|
||||
const paginates = isSearch || isCard || isDeleted
|
||||
|
||||
let query: unknown
|
||||
if (isSearch) query = params.searchQuery
|
||||
else if (isAutocomplete) query = params.autocompleteQuery
|
||||
|
||||
let collection: unknown
|
||||
if (isCollectionScoped) collection = params.collection
|
||||
else if (isCard) collection = params.cardCollection
|
||||
else if (isDeleted) collection = params.deletedCollection
|
||||
|
||||
return {
|
||||
apiKey: params.apiKey,
|
||||
query,
|
||||
collection,
|
||||
entityId: isLookup || isCard ? params.entityId : undefined,
|
||||
cardId: isCard ? params.cardId : undefined,
|
||||
fieldIds:
|
||||
operation === 'search_entities'
|
||||
? params.searchFieldIds
|
||||
: isSearch || isLookup
|
||||
? params.fieldIds
|
||||
: undefined,
|
||||
cardIds: isLookup ? params.cardIds : undefined,
|
||||
cardFieldIds: isCard ? params.cardFieldIds : undefined,
|
||||
order: isSearch ? params.order : undefined,
|
||||
cardOrder: isCard ? params.cardOrder : undefined,
|
||||
deletedAtOrder: isDeleted ? params.deletedAtOrder : undefined,
|
||||
collectionIds:
|
||||
isAutocomplete || isDeleted || isMetadata ? params.collectionIds : undefined,
|
||||
limit: isSearch || isAutocomplete || isCard || isDeleted ? params.limit : undefined,
|
||||
afterId: paginates ? params.afterId : undefined,
|
||||
beforeId: paginates ? params.beforeId : undefined,
|
||||
searchQuery: undefined,
|
||||
searchFieldIds: undefined,
|
||||
autocompleteQuery: undefined,
|
||||
cardCollection: undefined,
|
||||
deletedCollection: undefined,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Crunchbase operation to perform' },
|
||||
},
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Total matches for a search operation',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Search rows as [{uuid, properties}], autocomplete suggestions as [{identifier, facet_ids, short_description}], or deleted rows as [{deleted_at, identifier}]',
|
||||
},
|
||||
items: {
|
||||
type: 'json',
|
||||
description: 'Card items returned by Get Entity Card',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
description: 'UUID of the last row, to pass as After ID for the next page',
|
||||
},
|
||||
uuid: {
|
||||
type: 'string',
|
||||
description: 'Crunchbase UUID of a looked-up entity',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name of a looked-up entity',
|
||||
},
|
||||
permalink: {
|
||||
type: 'string',
|
||||
description: 'Crunchbase permalink of a looked-up entity',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested fields of a looked-up entity, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
description: 'Requested related-entity cards of a looked-up entity, keyed by card_id',
|
||||
},
|
||||
csv: {
|
||||
type: 'string',
|
||||
description: 'Field metadata as CSV, returned by Get Fields Metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const CrunchbaseBlockMeta = {
|
||||
tags: ['enrichment', 'data-analytics'],
|
||||
url: 'https://www.crunchbase.com',
|
||||
skills: [
|
||||
{
|
||||
name: 'enrich-company-record',
|
||||
description:
|
||||
'Resolve a company name or domain to its Crunchbase organization and pull firmographics onto the record. Use to fill in accounts before scoring or routing.',
|
||||
content:
|
||||
'# Enrich Company Record\n\nTurn a company name into a complete Crunchbase organization record.\n\n## Steps\n1. Run Autocomplete on the company name, constrained to the organizations collection, and take the permalink of the best match.\n2. Look up the organization with the fields the record needs — short_description, website_url, location_identifiers, categories, founded_on, num_employees_enum, operating_status.\n3. When the record needs the leadership team, add the founders card.\n4. Merge the returned fields onto the record, keeping existing values where Crunchbase returned nothing.\n\n## Output\nReport the matched permalink and the fields filled. Flag a name that autocompleted to nothing or matched more than one plausible company.',
|
||||
},
|
||||
{
|
||||
name: 'build-target-account-list',
|
||||
description:
|
||||
'Search Crunchbase organizations against an ideal customer profile and page the full result set into a list. Use for territory and campaign planning.',
|
||||
content:
|
||||
'# Build Target Account List\n\nTurn an ICP description into a paged list of matching companies.\n\n## Steps\n1. Translate the ICP into search predicates — categories with `includes`, location_identifiers with `includes`, num_employees_enum with `includes`, founded_on with `between`.\n2. Run Search Organizations with the columns the list needs and an order clause on rank_org.\n3. Page forward by passing the returned nextAfterId as After ID until a page comes back short.\n4. Write the deduplicated companies to a table.\n\n## Output\nReport the total match count, how many rows were retrieved, and the predicates used. Note when paging stopped early.',
|
||||
},
|
||||
{
|
||||
name: 'track-funding-rounds',
|
||||
description:
|
||||
'Find funding rounds announced in a window and report the company, stage, amount, and investors. Use for daily or weekly funding alerts.',
|
||||
content:
|
||||
'# Track Funding Rounds\n\nSurface new funding in a market and summarize each round.\n\n## Steps\n1. Search Funding Rounds with an `announced_on` `gte` predicate for the window, plus any category or amount filters.\n2. Request announced_on, investment_type, money_raised, funded_organization_identifier, investor_identifiers, and lead_investor_identifiers.\n3. Order by announced_on descending so the newest rounds lead.\n4. Summarize each round as company, stage, amount raised, and lead investors.\n\n## Output\nReport the rounds found in the window. State the window explicitly, and say so plainly when nothing was announced.',
|
||||
},
|
||||
{
|
||||
name: 'map-investor-portfolio',
|
||||
description:
|
||||
"Page an investor's participated investments and summarize their stage, sector, and check-size focus. Use for diligence and investor research.",
|
||||
content:
|
||||
'# Map Investor Portfolio\n\nAssemble what an investor actually backs from their investment card.\n\n## Steps\n1. Autocomplete the investor name against the organizations or people collection to get a permalink.\n2. Read the participated_investments card with Get Entity Card, requesting the announced date, amount, and funded organization.\n3. Page forward with the returned nextAfterId until the card is exhausted — an inline card request stops at 100 items.\n4. Group the investments by stage, sector, and year to describe the pattern.\n\n## Output\nReport the number of investments read, the stage and sector concentration, and the typical check size. Say when the portfolio was truncated by a license limit.',
|
||||
},
|
||||
{
|
||||
name: 'monitor-acquisitions',
|
||||
description:
|
||||
'Search acquisitions announced in a window and summarize acquirer, target, price, and terms. Use for competitive and market monitoring.',
|
||||
content:
|
||||
'# Monitor Acquisitions\n\nWatch consolidation in a market.\n\n## Steps\n1. Search Acquisitions with an `announced_on` `gte` predicate for the window.\n2. Request acquiree_identifier, acquirer_identifier, announced_on, price, acquisition_type, status, and terms.\n3. Order by announced_on descending.\n4. Summarize each deal, calling out an undisclosed price rather than reporting it as zero.\n\n## Output\nReport each deal as acquirer, target, price, and status. Note deals whose price Crunchbase does not publish.',
|
||||
},
|
||||
{
|
||||
name: 'discover-collection-fields',
|
||||
description:
|
||||
'List the fields a Crunchbase collection publishes before writing a search. Use when a predicate or column name is uncertain.',
|
||||
content:
|
||||
'# Discover Collection Fields\n\nGround a search in the fields the license actually exposes.\n\n## Steps\n1. Run Get Fields Metadata for the collections in question.\n2. Read the returned CSV for the field ids, their types, and their descriptions.\n3. Pick the columns for `field_ids` and the filterable fields for the query predicates.\n4. Run the search with those exact ids.\n\n## Output\nReport the fields chosen and why. Say explicitly when a field the request assumed does not exist on that collection or under this license.',
|
||||
},
|
||||
{
|
||||
name: 'prune-deleted-entities',
|
||||
description:
|
||||
'Read the Crunchbase deleted-entity feed and remove the matching rows from a mirrored copy. Use to keep a local mirror in step with the source.',
|
||||
content:
|
||||
'# Prune Deleted Entities\n\nKeep a mirrored copy from drifting away from Crunchbase.\n\n## Steps\n1. Run List Deleted Entities for the collections being mirrored, ordering by deletion time ascending.\n2. Page forward with the returned nextAfterId, keeping the last cursor as the watermark for the next run.\n3. Match each deleted uuid against the mirrored rows.\n4. Delete or tombstone the matches, leaving unmatched deletions alone.\n\n## Output\nReport how many deletions were read, how many matched local rows, and the cursor to resume from.',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
icon: CrunchbaseIcon,
|
||||
title: 'Crunchbase funding round watcher',
|
||||
prompt:
|
||||
'Build a workflow that runs every morning, searches Crunchbase for funding rounds announced in the last day in my target categories, and posts each round — company, stage, amount raised, and lead investors — to a Slack channel.',
|
||||
modules: ['scheduled', 'agent', 'workflows'],
|
||||
category: 'sales',
|
||||
tags: ['sales', 'research', 'automation'],
|
||||
alsoIntegrations: ['slack'],
|
||||
},
|
||||
{
|
||||
icon: Building,
|
||||
title: 'Crunchbase account enrichment',
|
||||
prompt:
|
||||
'Create a workflow that watches my accounts table for new company names, autocompletes each one against Crunchbase to resolve its permalink, looks up the organization, and writes headcount, headquarters, categories, and founding date back to the row.',
|
||||
modules: ['tables', 'agent', 'workflows'],
|
||||
category: 'sales',
|
||||
tags: ['sales', 'crm', 'enrichment'],
|
||||
},
|
||||
{
|
||||
icon: CrunchbaseIcon,
|
||||
title: 'Crunchbase ICP company list',
|
||||
prompt:
|
||||
'Build a workflow that searches Crunchbase organizations matching my ideal customer profile — category, headcount band, and headquarters region — pages through every result, and writes the company list to a table for the SDR team.',
|
||||
modules: ['tables', 'agent', 'workflows'],
|
||||
category: 'sales',
|
||||
tags: ['sales', 'research', 'automation'],
|
||||
},
|
||||
{
|
||||
icon: Sprout,
|
||||
title: 'Crunchbase investor portfolio brief',
|
||||
prompt:
|
||||
'Create an agent that takes an investor name, looks them up in Crunchbase, pages through their participated investments card, and writes a brief covering stage focus, check size, and the sectors they back most.',
|
||||
modules: ['agent', 'files', 'workflows'],
|
||||
category: 'operations',
|
||||
tags: ['research', 'finance'],
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Crunchbase founder finder',
|
||||
prompt:
|
||||
'Build a workflow that takes a list of target companies, looks each one up in Crunchbase with the founders card, and writes every founder with their title and LinkedIn to a table so I can start outreach.',
|
||||
modules: ['tables', 'agent', 'workflows'],
|
||||
category: 'sales',
|
||||
tags: ['sales', 'research', 'crm'],
|
||||
},
|
||||
{
|
||||
icon: CrunchbaseIcon,
|
||||
title: 'Crunchbase acquisition digest',
|
||||
prompt:
|
||||
'Create a scheduled workflow that searches Crunchbase for acquisitions announced this week in my industry, summarizes each deal — acquirer, target, price, and terms — and emails the digest to the strategy team every Friday.',
|
||||
modules: ['scheduled', 'agent', 'workflows'],
|
||||
category: 'operations',
|
||||
tags: ['research', 'reporting', 'automation'],
|
||||
alsoIntegrations: ['gmail'],
|
||||
},
|
||||
{
|
||||
icon: Search,
|
||||
title: 'Crunchbase competitor tracker',
|
||||
prompt:
|
||||
'Build a workflow that takes my competitor list, looks each company up in Crunchbase, compares headcount, operating status, and latest funding against what I stored last month, and alerts me in Slack when anything changed.',
|
||||
modules: ['tables', 'scheduled', 'workflows'],
|
||||
category: 'marketing',
|
||||
tags: ['research', 'monitoring', 'automation'],
|
||||
alsoIntegrations: ['slack'],
|
||||
},
|
||||
{
|
||||
icon: CrunchbaseIcon,
|
||||
title: 'Crunchbase inbound lead qualifier',
|
||||
prompt:
|
||||
'Create a workflow that runs when a demo request comes in, resolves the submitted company against Crunchbase, looks up its headcount, funding stage, and category, and routes the lead to the enterprise or self-serve queue based on what it finds.',
|
||||
modules: ['agent', 'workflows'],
|
||||
category: 'sales',
|
||||
tags: ['sales', 'crm', 'automation'],
|
||||
},
|
||||
{
|
||||
icon: Trash,
|
||||
title: 'Crunchbase mirror pruner',
|
||||
prompt:
|
||||
'Build a nightly workflow that reads the Crunchbase deleted-entity feed for organizations and people, matches each deletion against my mirrored companies table, and removes or tombstones the rows that no longer exist upstream.',
|
||||
modules: ['tables', 'scheduled', 'workflows'],
|
||||
category: 'operations',
|
||||
tags: ['data', 'automation', 'maintenance'],
|
||||
},
|
||||
{
|
||||
icon: ListChecks,
|
||||
title: 'Crunchbase query builder',
|
||||
prompt:
|
||||
'Create an agent that lists the fields a Crunchbase collection publishes, turns a plain-English request like "European fintechs that raised a Series B last quarter" into the matching search predicates, runs the search, and returns the results.',
|
||||
modules: ['agent', 'workflows'],
|
||||
category: 'productivity',
|
||||
tags: ['research', 'data'],
|
||||
},
|
||||
],
|
||||
} as const satisfies BlockMeta
|
||||
@@ -48,6 +48,7 @@ import { ConvexBlock, ConvexBlockMeta } from '@/blocks/blocks/convex'
|
||||
import { CredentialBlock } from '@/blocks/blocks/credential'
|
||||
import { CredentialGroupBlock } from '@/blocks/blocks/credential-group'
|
||||
import { CrowdStrikeBlock, CrowdStrikeBlockMeta } from '@/blocks/blocks/crowdstrike'
|
||||
import { CrunchbaseBlock, CrunchbaseBlockMeta } from '@/blocks/blocks/crunchbase'
|
||||
import { CursorBlock, CursorBlockMeta, CursorV2Block } from '@/blocks/blocks/cursor'
|
||||
import { DagsterBlock, DagsterBlockMeta } from '@/blocks/blocks/dagster'
|
||||
import { DatabricksBlock, DatabricksBlockMeta } from '@/blocks/blocks/databricks'
|
||||
@@ -409,6 +410,7 @@ export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
|
||||
credential: CredentialBlock,
|
||||
credential_group: CredentialGroupBlock,
|
||||
crowdstrike: CrowdStrikeBlock,
|
||||
crunchbase: CrunchbaseBlock,
|
||||
cursor: CursorBlock,
|
||||
cursor_v2: CursorV2Block,
|
||||
dagster: DagsterBlock,
|
||||
@@ -745,6 +747,7 @@ export const BLOCK_META_REGISTRY: Record<string, BlockMeta> = {
|
||||
context_dev: ContextDevBlockMeta,
|
||||
convex: ConvexBlockMeta,
|
||||
crowdstrike: CrowdStrikeBlockMeta,
|
||||
crunchbase: CrunchbaseBlockMeta,
|
||||
cursor: CursorBlockMeta,
|
||||
dagster: DagsterBlockMeta,
|
||||
databricks: DatabricksBlockMeta,
|
||||
|
||||
@@ -1489,15 +1489,15 @@ export function InstagramIcon(props: SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function CrunchbaseIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M21.6 0H2.4A2.41 2.41 0 0 0 0 2.4v19.2A2.41 2.41 0 0 0 2.4 24h19.2a2.41 2.41 0 0 0 2.4-2.4V2.4A2.41 2.41 0 0 0 21.6 0zM7.05 14.47A2.11 2.11 0 0 0 9.84 13.42h1.66a3.69 3.69 0 1 1 0-1.75H9.84a2.11 2.11 0 1 0-2.79 2.8zm11.35.845a3.55 3.55 0 0 1-1.06.63 3.68 3.68 0 0 1-3.39-.38v.38h-1.51V5.37h1.5v4.11a3.74 3.74 0 0 1 1.8-.63H16a3.67 3.67 0 0 1 2.39 6.46zm-.223-2.77a2.1 2.1 0 1 1-4.21 0 2.1 2.1 0 0 1 4.21 0z' />
|
||||
<svg {...props} viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198z'
|
||||
fill='#ffffff'
|
||||
/>
|
||||
<path
|
||||
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198zM9.396 19.286c1.411 0.646 3.078 0.021 3.724-1.391h2.214c-1.38 5.651-9.698 4.651-9.698-1.167 0-5.823 8.318-6.823 9.698-1.167h-2.214c-0.813-1.786-3.161-2.214-4.547-0.823-1.391 1.385-0.964 3.734 0.823 4.547zM24.521 20.411c-0.422 0.365-0.896 0.646-1.417 0.844-1.495 0.578-3.182 0.391-4.516-0.51v0.51h-2.016v-14.094h2v5.479c0.714-0.484 1.542-0.771 2.401-0.839h0.359c4.552-0.010 6.646 5.656 3.188 8.609zM24.224 16.724c0.031 1.573-1.234 2.87-2.807 2.87s-2.839-1.297-2.802-2.87c0.078-3.656 5.526-3.656 5.609 0z'
|
||||
fill='#0287d1'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
ContextDevIcon,
|
||||
ConvexIcon,
|
||||
CrowdStrikeIcon,
|
||||
CrunchbaseIcon,
|
||||
CursorIcon,
|
||||
DagsterIcon,
|
||||
DatabricksIcon,
|
||||
@@ -304,6 +305,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
context_dev: ContextDevIcon,
|
||||
convex: ConvexIcon,
|
||||
crowdstrike: CrowdStrikeIcon,
|
||||
crunchbase: CrunchbaseIcon,
|
||||
cursor_v2: CursorIcon,
|
||||
dagster: DagsterIcon,
|
||||
databricks: DatabricksIcon,
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import type {
|
||||
CrunchbaseAutocompleteEntity,
|
||||
CrunchbaseAutocompleteParams,
|
||||
CrunchbaseAutocompleteResponse,
|
||||
} from '@/tools/crunchbase/types'
|
||||
import {
|
||||
AUTOCOMPLETE_LIMIT_MAX,
|
||||
appendCsvParam,
|
||||
assertCollections,
|
||||
CRUNCHBASE_API_BASE,
|
||||
CRUNCHBASE_AUTOCOMPLETE_COLLECTIONS,
|
||||
clampLimit,
|
||||
crunchbaseError,
|
||||
crunchbaseHeaders,
|
||||
parseIdListParam,
|
||||
readJson,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseAutocompleteTool: ToolConfig<
|
||||
CrunchbaseAutocompleteParams,
|
||||
CrunchbaseAutocompleteResponse
|
||||
> = {
|
||||
id: 'crunchbase_autocomplete',
|
||||
name: 'Crunchbase Autocomplete',
|
||||
description:
|
||||
'Suggest Crunchbase entities matching a typed query, returning the permalinks and UUIDs the lookup and search operations take.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Text to autocomplete against, e.g. "airbnb"',
|
||||
},
|
||||
collectionIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collections to search, e.g. ["organizations","people"]. One or more of: addresses, categories, category_groups, degrees, diversity_spotlights, event_appearances, events, ipos, jobs, locations, organizations, ownerships, people, principals. Defaults to every collection.',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Suggestions to return, max 25 (default 10)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const query = params.query?.trim()
|
||||
if (!query) throw new Error('Crunchbase "query" is required for autocomplete')
|
||||
|
||||
const search = new URLSearchParams({ query })
|
||||
appendCsvParam(
|
||||
search,
|
||||
'collection_ids',
|
||||
assertCollections(
|
||||
parseIdListParam(params.collectionIds, 'collectionIds'),
|
||||
CRUNCHBASE_AUTOCOMPLETE_COLLECTIONS,
|
||||
'collectionIds'
|
||||
)
|
||||
)
|
||||
const limit = clampLimit(params.limit, AUTOCOMPLETE_LIMIT_MAX)
|
||||
if (limit !== undefined) search.set('limit', String(limit))
|
||||
|
||||
return `${CRUNCHBASE_API_BASE}/autocompletes?${search.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
const data = await readJson<{ entities?: CrunchbaseAutocompleteEntity[] }>(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entities: Array.isArray(data.entities) ? data.entities : [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Suggestions as [{identifier: {uuid, value, permalink, image_id, entity_def_id}, facet_ids, short_description}]',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { crunchbaseAutocompleteTool } from '@/tools/crunchbase/autocomplete'
|
||||
import { crunchbaseGetEntityTool } from '@/tools/crunchbase/get_entity'
|
||||
import { crunchbaseGetEntityCardTool } from '@/tools/crunchbase/get_entity_card'
|
||||
import { crunchbaseGetFieldsMetadataTool } from '@/tools/crunchbase/get_fields_metadata'
|
||||
import { crunchbaseGetOrganizationTool } from '@/tools/crunchbase/get_organization'
|
||||
import { crunchbaseListDeletedEntitiesTool } from '@/tools/crunchbase/list_deleted_entities'
|
||||
import { crunchbaseSearchEntitiesTool } from '@/tools/crunchbase/search_entities'
|
||||
import { crunchbaseSearchOrganizationsTool } from '@/tools/crunchbase/search_organizations'
|
||||
import { extractErrorMessage } from '@/tools/error-extractors'
|
||||
|
||||
const buildUrl = (tool: { request: { url: unknown } }, params: Record<string, unknown>) =>
|
||||
(tool.request.url as (p: Record<string, unknown>) => string)(params)
|
||||
|
||||
const buildBody = (tool: { request: { body?: unknown } }, params: Record<string, unknown>) =>
|
||||
(tool.request.body as (p: Record<string, unknown>) => Record<string, unknown>)(params)
|
||||
|
||||
const jsonResponse = (body: unknown, init?: ResponseInit) =>
|
||||
new Response(JSON.stringify(body), { status: 200, ...init })
|
||||
|
||||
describe('crunchbase request building', () => {
|
||||
it('authenticates with the documented header, not a query param', () => {
|
||||
const headers = crunchbaseSearchOrganizationsTool.request.headers({ apiKey: 'secret' } as never)
|
||||
expect(headers['X-cb-user-key']).toBe('secret')
|
||||
expect(crunchbaseSearchOrganizationsTool.request.url).not.toContain('user_key')
|
||||
})
|
||||
|
||||
it('parses a JSON-string query the same as an already-parsed array', () => {
|
||||
const predicate = {
|
||||
type: 'predicate',
|
||||
field_id: 'categories',
|
||||
operator_id: 'includes',
|
||||
values: ['biotechnology'],
|
||||
}
|
||||
const fromString = buildBody(crunchbaseSearchOrganizationsTool, {
|
||||
apiKey: 'k',
|
||||
query: JSON.stringify([predicate]),
|
||||
})
|
||||
const fromArray = buildBody(crunchbaseSearchOrganizationsTool, {
|
||||
apiKey: 'k',
|
||||
query: [predicate],
|
||||
})
|
||||
|
||||
expect(fromString.query).toEqual([predicate])
|
||||
expect(fromString).toEqual(fromArray)
|
||||
})
|
||||
|
||||
it('falls back to the collection default field list', () => {
|
||||
const body = buildBody(crunchbaseSearchOrganizationsTool, { apiKey: 'k', query: '[]' })
|
||||
expect(body.field_ids).toContain('identifier')
|
||||
expect(body.field_ids).toContain('num_employees_enum')
|
||||
expect(body.limit).toBe(100)
|
||||
})
|
||||
|
||||
it('clamps limit into the documented range', () => {
|
||||
expect(
|
||||
buildBody(crunchbaseSearchOrganizationsTool, { apiKey: 'k', query: '[]', limit: 9000 })
|
||||
).toMatchObject({ limit: 1000 })
|
||||
expect(
|
||||
buildBody(crunchbaseSearchOrganizationsTool, { apiKey: 'k', query: '[]', limit: '25' })
|
||||
).toMatchObject({ limit: 25 })
|
||||
})
|
||||
|
||||
it('rejects a malformed predicate before spending a round trip', () => {
|
||||
expect(() =>
|
||||
buildBody(crunchbaseSearchOrganizationsTool, {
|
||||
apiKey: 'k',
|
||||
query: '[{"field_id":"categories"}]',
|
||||
})
|
||||
).toThrow(/missing "operator_id"/)
|
||||
|
||||
expect(() =>
|
||||
buildBody(crunchbaseSearchOrganizationsTool, { apiKey: 'k', query: 'categories,name' })
|
||||
).toThrow(/must be a JSON array/)
|
||||
})
|
||||
|
||||
it('rejects both cursors at once', () => {
|
||||
expect(() =>
|
||||
buildBody(crunchbaseSearchOrganizationsTool, {
|
||||
apiKey: 'k',
|
||||
query: '[]',
|
||||
afterId: 'a',
|
||||
beforeId: 'b',
|
||||
})
|
||||
).toThrow(/either "afterId" or "beforeId"/)
|
||||
})
|
||||
|
||||
it('requires an explicit field list on the generic search', () => {
|
||||
expect(() =>
|
||||
buildBody(crunchbaseSearchEntitiesTool, { apiKey: 'k', collection: 'events', query: '[]' })
|
||||
).toThrow(/"fieldIds" is required/)
|
||||
})
|
||||
|
||||
it('rejects a collection the API does not publish', () => {
|
||||
expect(() =>
|
||||
buildUrl(crunchbaseSearchEntitiesTool, { apiKey: 'k', collection: 'unicorns', query: '[]' })
|
||||
).toThrow(/"collection" must be one of/)
|
||||
})
|
||||
|
||||
it('sends field_ids and card_ids as one comma-joined value', () => {
|
||||
const url = buildUrl(crunchbaseGetOrganizationTool, {
|
||||
apiKey: 'k',
|
||||
entityId: ' tesla-motors ',
|
||||
fieldIds: ['identifier', 'name'],
|
||||
cardIds: 'founders, headquarters_address',
|
||||
})
|
||||
expect(url).toContain('/v4/data/entities/organizations/tesla-motors?')
|
||||
expect(url).toContain('field_ids=identifier%2Cname')
|
||||
expect(url).toContain('card_ids=founders%2Cheadquarters_address')
|
||||
})
|
||||
|
||||
it('omits field_ids on a generic lookup so the API picks its own projection', () => {
|
||||
const url = buildUrl(crunchbaseGetEntityTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'events',
|
||||
entityId: 'techcrunch-disrupt',
|
||||
})
|
||||
expect(url).toBe('https://api.crunchbase.com/v4/data/entities/events/techcrunch-disrupt')
|
||||
})
|
||||
|
||||
it('builds the single-card path with its own cursor params', () => {
|
||||
const url = buildUrl(crunchbaseGetEntityCardTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
cardFieldIds: '["identifier","announced_on"]',
|
||||
cardOrder: 'funding_round_money_raised desc',
|
||||
afterId: 'cursor-1',
|
||||
})
|
||||
expect(url).toContain('/entities/organizations/sequoia-capital/cards/participated_investments?')
|
||||
expect(url).toContain('card_field_ids=identifier%2Cannounced_on')
|
||||
expect(url).toContain('order=funding_round_money_raised+desc')
|
||||
expect(url).toContain('after_id=cursor-1')
|
||||
})
|
||||
|
||||
it('caps a card page at the documented 100-item maximum', () => {
|
||||
const url = buildUrl(crunchbaseGetEntityCardTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
limit: 1000,
|
||||
})
|
||||
/* Substring-matching "limit=100" would also pass on "limit=1000" — the exact
|
||||
parameter value is the only assertion that can actually fail here. */
|
||||
expect(new URL(url).searchParams.get('limit')).toBe('100')
|
||||
})
|
||||
|
||||
it('keeps the cursor field when cardFieldIds would have dropped it', () => {
|
||||
const narrowed = buildUrl(crunchbaseGetEntityCardTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
cardFieldIds: '["announced_on"]',
|
||||
})
|
||||
expect(narrowed).toContain('card_field_ids=announced_on%2Cidentifier')
|
||||
|
||||
const alreadyPresent = buildUrl(crunchbaseGetEntityCardTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
cardFieldIds: '["uuid","announced_on"]',
|
||||
})
|
||||
expect(alreadyPresent).toContain('card_field_ids=uuid%2Cannounced_on')
|
||||
})
|
||||
|
||||
it('rejects both cursors on every paged endpoint, not just search', () => {
|
||||
expect(() =>
|
||||
buildUrl(crunchbaseGetEntityCardTool, {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'founders',
|
||||
afterId: 'a',
|
||||
beforeId: 'b',
|
||||
})
|
||||
).toThrow(/either "afterId" or "beforeId"/)
|
||||
|
||||
expect(() =>
|
||||
buildUrl(crunchbaseListDeletedEntitiesTool, { apiKey: 'k', afterId: 'a', beforeId: 'b' })
|
||||
).toThrow(/either "afterId" or "beforeId"/)
|
||||
})
|
||||
|
||||
it('scopes the deleted feed by path when one collection is chosen', () => {
|
||||
expect(
|
||||
buildUrl(crunchbaseListDeletedEntitiesTool, { apiKey: 'k', collection: 'organizations' })
|
||||
).toBe('https://api.crunchbase.com/v4/data/deleted_entities/organizations')
|
||||
|
||||
expect(
|
||||
buildUrl(crunchbaseListDeletedEntitiesTool, {
|
||||
apiKey: 'k',
|
||||
collectionIds: ['organizations', 'people'],
|
||||
deletedAtOrder: 'desc',
|
||||
})
|
||||
).toBe(
|
||||
'https://api.crunchbase.com/v4/data/deleted_entities?collection_ids=organizations%2Cpeople&deleted_at_order=desc'
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects a collection the constrained endpoints do not accept', () => {
|
||||
expect(() =>
|
||||
buildUrl(crunchbaseAutocompleteTool, {
|
||||
apiKey: 'k',
|
||||
query: 'airbnb',
|
||||
collectionIds: ['funds'],
|
||||
})
|
||||
).toThrow(/unsupported collection/)
|
||||
})
|
||||
|
||||
it('reads fields metadata from the /md path, not /data', () => {
|
||||
expect(
|
||||
buildUrl(crunchbaseGetFieldsMetadataTool, { apiKey: 'k', collectionIds: ['organizations'] })
|
||||
).toBe(
|
||||
'https://api.crunchbase.com/v4/md/applications/crunchbase/fields?collection_ids=organizations'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('crunchbase response mapping', () => {
|
||||
it('carries the last uuid forward as the next page cursor', async () => {
|
||||
const result = await crunchbaseSearchOrganizationsTool.transformResponse!(
|
||||
jsonResponse({
|
||||
count: 812,
|
||||
entities: [
|
||||
{ uuid: 'a', properties: { name: 'Alpha' } },
|
||||
{ uuid: 'b', properties: { name: 'Beta' } },
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
expect(result.output.count).toBe(812)
|
||||
expect(result.output.entities).toHaveLength(2)
|
||||
expect(result.output.nextAfterId).toBe('b')
|
||||
})
|
||||
|
||||
it('reports an empty page as empty rather than as a missing count', async () => {
|
||||
const result = await crunchbaseSearchOrganizationsTool.transformResponse!(
|
||||
jsonResponse({ count: 0, entities: [] })
|
||||
)
|
||||
expect(result.output.count).toBe(0)
|
||||
expect(result.output.entities).toEqual([])
|
||||
expect(result.output.nextAfterId).toBeNull()
|
||||
})
|
||||
|
||||
it('lifts identity out of the dynamic property bag', async () => {
|
||||
const result = await crunchbaseGetOrganizationTool.transformResponse!(
|
||||
jsonResponse({
|
||||
properties: {
|
||||
identifier: {
|
||||
uuid: 'e1a1',
|
||||
value: 'Tesla',
|
||||
permalink: 'tesla-motors',
|
||||
entity_def_id: 'organization',
|
||||
},
|
||||
},
|
||||
cards: { founders: [{ uuid: 'p1' }] },
|
||||
})
|
||||
)
|
||||
|
||||
expect(result.output.uuid).toBe('e1a1')
|
||||
expect(result.output.name).toBe('Tesla')
|
||||
expect(result.output.permalink).toBe('tesla-motors')
|
||||
expect(result.output.cards).toEqual({ founders: [{ uuid: 'p1' }] })
|
||||
})
|
||||
|
||||
it('degrades identity to null when field_ids dropped the identifier', async () => {
|
||||
const result = await crunchbaseGetOrganizationTool.transformResponse!(
|
||||
jsonResponse({ properties: { short_description: 'Electric cars' } })
|
||||
)
|
||||
expect(result.output.uuid).toBeNull()
|
||||
expect(result.output.name).toBeNull()
|
||||
expect(result.output.permalink).toBeNull()
|
||||
expect(result.output.cards).toBeNull()
|
||||
})
|
||||
|
||||
it('reads a card page from under its own card id', async () => {
|
||||
const params = {
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'sequoia-capital',
|
||||
cardId: 'participated_investments',
|
||||
}
|
||||
const result = await crunchbaseGetEntityCardTool.transformResponse!(
|
||||
jsonResponse({
|
||||
properties: { identifier: { uuid: 'org-1' } },
|
||||
cards: {
|
||||
participated_investments: [
|
||||
{ identifier: { uuid: 'i1' } },
|
||||
{ identifier: { uuid: 'i2' } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
params as never
|
||||
)
|
||||
|
||||
expect(result.output.items).toHaveLength(2)
|
||||
expect(result.output.nextAfterId).toBe('i2')
|
||||
})
|
||||
|
||||
it('keys the card page by the trimmed id the URL used', async () => {
|
||||
const result = await crunchbaseGetEntityCardTool.transformResponse!(
|
||||
jsonResponse({ cards: { founders: [{ identifier: { uuid: 'p1' } }] } }),
|
||||
{
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'tesla-motors',
|
||||
cardId: ' founders ',
|
||||
} as never
|
||||
)
|
||||
|
||||
expect(result.output.items).toHaveLength(1)
|
||||
expect(result.output.nextAfterId).toBe('p1')
|
||||
})
|
||||
|
||||
it('reports an undocumented card shape as empty rather than inventing a row', async () => {
|
||||
const result = await crunchbaseGetEntityCardTool.transformResponse!(
|
||||
jsonResponse({ cards: { founders: { items: [{ uuid: 'p1' }], paging: {} } } }),
|
||||
{
|
||||
apiKey: 'k',
|
||||
collection: 'organizations',
|
||||
entityId: 'tesla-motors',
|
||||
cardId: 'founders',
|
||||
} as never
|
||||
)
|
||||
|
||||
expect(result.output.items).toEqual([])
|
||||
expect(result.output.nextAfterId).toBeNull()
|
||||
})
|
||||
|
||||
it('passes the fields-metadata CSV through verbatim', async () => {
|
||||
const csv = 'collection_id,field_id,type\norganizations,name,text\n'
|
||||
const result = await crunchbaseGetFieldsMetadataTool.transformResponse!(
|
||||
new Response(csv, { status: 200 })
|
||||
)
|
||||
expect(result.output.csv).toBe(csv)
|
||||
})
|
||||
})
|
||||
|
||||
describe('crunchbase error reporting', () => {
|
||||
/*
|
||||
* The tool layer throws on a non-ok response before `transformResponse` runs,
|
||||
* so the message a user sees comes from the extractor — and Crunchbase answers
|
||||
* with a bare array, which nothing else in the registry reads.
|
||||
*/
|
||||
it('reads the message out of the top-level error array', () => {
|
||||
expect(
|
||||
extractErrorMessage(
|
||||
{ status: 401, data: [{ status: 401, code: 'LA401', message: 'Unauthorized user_key' }] },
|
||||
crunchbaseSearchOrganizationsTool.errorExtractor
|
||||
)
|
||||
).toBe('Unauthorized user_key')
|
||||
})
|
||||
|
||||
it('falls back to the status when the body carries no message', () => {
|
||||
expect(
|
||||
extractErrorMessage(
|
||||
{ status: 500, data: [] },
|
||||
crunchbaseSearchOrganizationsTool.errorExtractor
|
||||
)
|
||||
).toBe('Request failed with status 500')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { CrunchbaseEntityParams, CrunchbaseEntityResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildEntityUrl,
|
||||
crunchbaseHeaders,
|
||||
DEFAULT_ACQUISITION_FIELD_IDS,
|
||||
transformEntityResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseGetAcquisitionTool: ToolConfig<
|
||||
CrunchbaseEntityParams,
|
||||
CrunchbaseEntityResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_acquisition',
|
||||
name: 'Crunchbase Get Acquisition',
|
||||
description:
|
||||
'Look up a single Crunchbase acquisition by permalink or UUID, returning the requested fields and related cards.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Acquisition permalink or UUID',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Acquisition fields to return, e.g. ["identifier","acquiree_identifier","acquirer_identifier","price"]. Defaults to identifier, acquiree_identifier, acquirer_identifier, announced_on, completed_on, price, acquisition_type, status, terms, short_description, permalink.',
|
||||
},
|
||||
cardIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Related-entity cards to include, e.g. ["acquiree_organization","acquirer_organization"]. Available: acquiree_organization, acquirer_organization, fields, press_references. A card returns at most 100 items.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildEntityUrl('acquisitions', params, DEFAULT_ACQUISITION_FIELD_IDS),
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: transformEntityResponse,
|
||||
|
||||
outputs: {
|
||||
uuid: { type: 'string', nullable: true, description: 'Crunchbase UUID of the acquisition' },
|
||||
name: { type: 'string', nullable: true, description: 'Acquisition name' },
|
||||
permalink: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Crunchbase permalink of the acquisition',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested acquisition fields, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
nullable: true,
|
||||
description: 'Requested related-entity cards, keyed by card_id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { CrunchbaseEntityParams, CrunchbaseEntityResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
assertCollection,
|
||||
buildEntityUrl,
|
||||
CRUNCHBASE_COLLECTIONS,
|
||||
crunchbaseHeaders,
|
||||
transformEntityResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CrunchbaseGetEntityParams extends CrunchbaseEntityParams {
|
||||
collection: string
|
||||
}
|
||||
|
||||
export const crunchbaseGetEntityTool: ToolConfig<
|
||||
CrunchbaseGetEntityParams,
|
||||
CrunchbaseEntityResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_entity',
|
||||
name: 'Crunchbase Get Entity',
|
||||
description:
|
||||
'Look up a single entity in any Crunchbase collection — events, jobs, ipos, funds, investments, press references, insights, predictions, and more — by permalink or UUID.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collection the entity belongs to. One of: acquisition_predictions, acquisitions, addresses, awards, categories, category_groups, closure_predictions, current_valuation_estimates, degrees, diversity_spotlights, event_appearances, events, funding_predictions, funding_rounds, funds, growth_insights, growth_predictions, investments, investor_insights, investor_matches, ipo_predictions, ipos, jobs, key_employee_changes, layoff_predictions, layoffs, legal_proceedings, locations, market_insight_reasons, market_insights, micro_categories, org_similarities, organizations, ownerships, partnership_announcements, people, press_references, principals, product_launches, product_similarities, products, remain_private_predictions, research_insights.',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity permalink or UUID',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields to return for the chosen collection, e.g. ["identifier","short_description"]. Leave empty to accept the default projection the API returns; list the valid ids with the Get Fields Metadata operation.',
|
||||
},
|
||||
cardIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Related-entity cards to include. The valid ids differ per collection, and a card returns at most 100 items — use the Get Entity Card operation to page past that.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildEntityUrl(
|
||||
assertCollection(params.collection, CRUNCHBASE_COLLECTIONS, 'collection'),
|
||||
params
|
||||
),
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: transformEntityResponse,
|
||||
|
||||
outputs: {
|
||||
uuid: { type: 'string', nullable: true, description: 'Crunchbase UUID of the entity' },
|
||||
name: { type: 'string', nullable: true, description: 'Name of the entity' },
|
||||
permalink: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Crunchbase permalink of the entity',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested entity fields, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
nullable: true,
|
||||
description: 'Requested related-entity cards, keyed by card_id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import type { CrunchbaseProperties } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
assertCollection,
|
||||
assertSingleCursor,
|
||||
CARD_LIMIT_MAX,
|
||||
CRUNCHBASE_API_BASE,
|
||||
CRUNCHBASE_CARD_COLLECTIONS,
|
||||
clampLimit,
|
||||
crunchbaseError,
|
||||
crunchbaseHeaders,
|
||||
parseIdListParam,
|
||||
readJson,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
interface CrunchbaseGetEntityCardParams {
|
||||
apiKey: string
|
||||
collection: string
|
||||
entityId: string
|
||||
cardId: string
|
||||
cardFieldIds?: string[] | string
|
||||
cardOrder?: string
|
||||
limit?: number | string
|
||||
afterId?: string
|
||||
beforeId?: string
|
||||
}
|
||||
|
||||
interface CrunchbaseGetEntityCardResponse extends ToolResponse {
|
||||
output: {
|
||||
items: CrunchbaseProperties[]
|
||||
properties: CrunchbaseProperties
|
||||
nextAfterId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarantees the page carries the field its cursor is read from.
|
||||
*
|
||||
* `nextAfterId` comes off the last item's `uuid` / `identifier.uuid`, so a caller
|
||||
* narrowing `card_field_ids` to, say, `["announced_on"]` would get a full page
|
||||
* and a null cursor — and a paging loop would stop after the first page with
|
||||
* rows still unread.
|
||||
*/
|
||||
function withCursorField(cardFieldIds: string[] | undefined): string[] | undefined {
|
||||
if (!cardFieldIds?.length) return cardFieldIds
|
||||
if (cardFieldIds.includes('identifier') || cardFieldIds.includes('uuid')) return cardFieldIds
|
||||
return [...cardFieldIds, 'identifier']
|
||||
}
|
||||
|
||||
export const crunchbaseGetEntityCardTool: ToolConfig<
|
||||
CrunchbaseGetEntityCardParams,
|
||||
CrunchbaseGetEntityCardResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_entity_card',
|
||||
name: 'Crunchbase Get Entity Card',
|
||||
description:
|
||||
"Page through one related-entity card of a Crunchbase entity — an investor's investments, a company's founders, a round's investors — past the 100-item cap an inline card request returns.",
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collection the entity belongs to. One of: acquisitions, addresses, categories, category_groups, degrees, event_appearances, events, funding_rounds, funds, investments, ipos, jobs, market_insights, micro_categories, organizations, ownerships, people.',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity permalink or UUID',
|
||||
},
|
||||
cardId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Card to page through, e.g. "participated_investments" on a person, "founders" on an organization, or "investors" on a funding round. Valid ids differ per collection.',
|
||||
},
|
||||
cardFieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields to return on each card item, e.g. ["identifier","announced_on","money_raised"]. The identifier is always requested alongside these, because the next-page cursor is read from it.',
|
||||
},
|
||||
cardOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort expression for the card, e.g. "funding_round_money_raised desc"',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Card items to return per page, 1-100',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'UUID of the last card item on the current page, to fetch the next page',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'UUID of the first card item on the current page, to fetch the previous page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const collection = assertCollection(
|
||||
params.collection,
|
||||
CRUNCHBASE_CARD_COLLECTIONS,
|
||||
'collection'
|
||||
)
|
||||
const entityId = params.entityId?.trim()
|
||||
if (!entityId) throw new Error('Crunchbase "entityId" (uuid or permalink) is required')
|
||||
const cardId = params.cardId?.trim()
|
||||
if (!cardId) throw new Error('Crunchbase "cardId" is required')
|
||||
|
||||
assertSingleCursor(params.afterId, params.beforeId)
|
||||
|
||||
const search = new URLSearchParams()
|
||||
const cardFieldIds = withCursorField(parseIdListParam(params.cardFieldIds, 'cardFieldIds'))
|
||||
if (cardFieldIds?.length) search.set('card_field_ids', cardFieldIds.join(','))
|
||||
if (params.cardOrder) search.set('order', params.cardOrder)
|
||||
const limit = clampLimit(params.limit, CARD_LIMIT_MAX)
|
||||
if (limit !== undefined) search.set('limit', String(limit))
|
||||
if (params.afterId) search.set('after_id', params.afterId)
|
||||
if (params.beforeId) search.set('before_id', params.beforeId)
|
||||
|
||||
const qs = search.toString()
|
||||
return `${CRUNCHBASE_API_BASE}/entities/${collection}/${encodeURIComponent(entityId)}/cards/${encodeURIComponent(cardId)}${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
const data = await readJson<{
|
||||
properties?: CrunchbaseProperties
|
||||
cards?: Record<string, unknown>
|
||||
}>(response)
|
||||
|
||||
/* The endpoint answers with the entity wrapper, so the page sits under the
|
||||
requested card id rather than at the top level — keyed by the same trimmed
|
||||
id the URL used, or a pasted " founders " would read back as empty. */
|
||||
const card = data.cards?.[params?.cardId?.trim() ?? '']
|
||||
|
||||
/* Every card this endpoint serves is typed as an array of entities. Wrapping
|
||||
a non-array as a single item would invent a one-row page out of a shape we
|
||||
do not understand, so an unexpected value reports as empty instead. */
|
||||
const items = Array.isArray(card) ? (card as CrunchbaseProperties[]) : []
|
||||
/* A card item carries its uuid at the top level only when `card_field_ids`
|
||||
asked for it; otherwise the identifier object is the one place it lives. */
|
||||
const last = items[items.length - 1]
|
||||
const identifier = last?.identifier as { uuid?: unknown } | undefined
|
||||
const lastUuid =
|
||||
typeof last?.uuid === 'string'
|
||||
? last.uuid
|
||||
: typeof identifier?.uuid === 'string'
|
||||
? identifier.uuid
|
||||
: null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items,
|
||||
properties: data.properties ?? {},
|
||||
nextAfterId: lastUuid,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'json',
|
||||
description: 'Card items for this page, each holding the requested card_field_ids',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Properties of the parent entity returned alongside the card',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last card item, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
appendCsvParam,
|
||||
assertCollections,
|
||||
CRUNCHBASE_API_ROOT,
|
||||
CRUNCHBASE_METADATA_COLLECTIONS,
|
||||
crunchbaseError,
|
||||
parseIdListParam,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
interface CrunchbaseGetFieldsMetadataParams {
|
||||
apiKey: string
|
||||
collectionIds?: string[] | string
|
||||
}
|
||||
|
||||
interface CrunchbaseGetFieldsMetadataResponse extends ToolResponse {
|
||||
output: {
|
||||
csv: string
|
||||
}
|
||||
}
|
||||
|
||||
export const crunchbaseGetFieldsMetadataTool: ToolConfig<
|
||||
CrunchbaseGetFieldsMetadataParams,
|
||||
CrunchbaseGetFieldsMetadataResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_fields_metadata',
|
||||
name: 'Crunchbase Get Fields Metadata',
|
||||
description:
|
||||
'List the field ids, types, and descriptions each Crunchbase collection publishes, which is how the field_ids and query predicates of the other operations are discovered.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
collectionIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collections to describe, e.g. ["organizations","people"]. One or more of: addresses, categories, category_groups, degrees, diversity_spotlights, event_appearances, events, ipos, jobs, locations, organizations, ownerships, people, principals. Defaults to every collection.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const search = new URLSearchParams()
|
||||
appendCsvParam(
|
||||
search,
|
||||
'collection_ids',
|
||||
assertCollections(
|
||||
parseIdListParam(params.collectionIds, 'collectionIds'),
|
||||
CRUNCHBASE_METADATA_COLLECTIONS,
|
||||
'collectionIds'
|
||||
)
|
||||
)
|
||||
const qs = search.toString()
|
||||
return `${CRUNCHBASE_API_ROOT}/md/applications/crunchbase/fields${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'X-cb-user-key': params.apiKey,
|
||||
Accept: 'text/csv',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
/* This endpoint answers in CSV rather than JSON, so the body is passed
|
||||
through verbatim for a downstream parser to read. */
|
||||
return {
|
||||
success: true,
|
||||
output: { csv: await response.text() },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
csv: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Field metadata as CSV, one row per field with its collection, id, type, and description',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { CrunchbaseEntityParams, CrunchbaseEntityResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildEntityUrl,
|
||||
crunchbaseHeaders,
|
||||
DEFAULT_FUNDING_ROUND_FIELD_IDS,
|
||||
transformEntityResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseGetFundingRoundTool: ToolConfig<
|
||||
CrunchbaseEntityParams,
|
||||
CrunchbaseEntityResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_funding_round',
|
||||
name: 'Crunchbase Get Funding Round',
|
||||
description:
|
||||
'Look up a single Crunchbase funding round by permalink or UUID, returning the requested fields and related cards.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Funding round permalink (e.g. "tesla-motors-series-c--12345678") or UUID',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Funding round fields to return, e.g. ["identifier","announced_on","money_raised","investor_identifiers"]. Defaults to identifier, announced_on, investment_type, investment_stage, money_raised, funded_organization_identifier, investor_identifiers, lead_investor_identifiers, num_investors, short_description, permalink.',
|
||||
},
|
||||
cardIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Related-entity cards to include, e.g. ["investors","organization"]. Available: fields, investments, investors, lead_investors, organization, partners, press_references. A card returns at most 100 items.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildEntityUrl('funding_rounds', params, DEFAULT_FUNDING_ROUND_FIELD_IDS),
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: transformEntityResponse,
|
||||
|
||||
outputs: {
|
||||
uuid: { type: 'string', nullable: true, description: 'Crunchbase UUID of the funding round' },
|
||||
name: { type: 'string', nullable: true, description: 'Funding round name' },
|
||||
permalink: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Crunchbase permalink of the funding round',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested funding round fields, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
nullable: true,
|
||||
description: 'Requested related-entity cards, keyed by card_id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { CrunchbaseEntityParams, CrunchbaseEntityResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildEntityUrl,
|
||||
crunchbaseHeaders,
|
||||
DEFAULT_ORGANIZATION_FIELD_IDS,
|
||||
transformEntityResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseGetOrganizationTool: ToolConfig<
|
||||
CrunchbaseEntityParams,
|
||||
CrunchbaseEntityResponse
|
||||
> = {
|
||||
id: 'crunchbase_get_organization',
|
||||
name: 'Crunchbase Get Organization',
|
||||
description:
|
||||
'Look up a single Crunchbase organization by permalink or UUID, returning the requested fields and related cards.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization permalink (e.g. "tesla-motors") or UUID',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Organization fields to return, e.g. ["identifier","name","founded_on","categories"]. Defaults to identifier, name, short_description, website_url, linkedin, location_identifiers, categories, founded_on, num_employees_enum, operating_status, rank_org, permalink.',
|
||||
},
|
||||
cardIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Related-entity cards to include, e.g. ["founders","headquarters_address"]. Available on every license tier: child_organizations, child_ownerships, event_appearances, fields, founders, headquarters_address, parent_organization, parent_ownership. A card returns at most 100 items.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildEntityUrl('organizations', params, DEFAULT_ORGANIZATION_FIELD_IDS),
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: transformEntityResponse,
|
||||
|
||||
outputs: {
|
||||
uuid: { type: 'string', nullable: true, description: 'Crunchbase UUID of the organization' },
|
||||
name: { type: 'string', nullable: true, description: 'Organization name' },
|
||||
permalink: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Crunchbase permalink of the organization',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested organization fields, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
nullable: true,
|
||||
description: 'Requested related-entity cards, keyed by card_id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { CrunchbaseEntityParams, CrunchbaseEntityResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildEntityUrl,
|
||||
crunchbaseHeaders,
|
||||
DEFAULT_PERSON_FIELD_IDS,
|
||||
transformEntityResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseGetPersonTool: ToolConfig<CrunchbaseEntityParams, CrunchbaseEntityResponse> =
|
||||
{
|
||||
id: 'crunchbase_get_person',
|
||||
name: 'Crunchbase Get Person',
|
||||
description:
|
||||
'Look up a single Crunchbase person by permalink or UUID, returning the requested fields and related cards.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
entityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Person permalink (e.g. "elon-musk") or UUID',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Person fields to return, e.g. ["identifier","name","primary_job_title","primary_organization"]. Defaults to identifier, name, first_name, last_name, primary_job_title, primary_organization, short_description, location_identifiers, linkedin, rank_person, permalink.',
|
||||
},
|
||||
cardIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Related-entity cards to include, e.g. ["jobs","primary_organization"]. Available: degrees, event_appearances, fields, founded_organizations, jobs, primary_job, primary_organization. A card returns at most 100 items.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildEntityUrl('people', params, DEFAULT_PERSON_FIELD_IDS),
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: transformEntityResponse,
|
||||
|
||||
outputs: {
|
||||
uuid: { type: 'string', nullable: true, description: 'Crunchbase UUID of the person' },
|
||||
name: { type: 'string', nullable: true, description: 'Full name of the person' },
|
||||
permalink: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Crunchbase permalink of the person',
|
||||
},
|
||||
properties: {
|
||||
type: 'json',
|
||||
description: 'Requested person fields, keyed by field_id',
|
||||
},
|
||||
cards: {
|
||||
type: 'json',
|
||||
nullable: true,
|
||||
description: 'Requested related-entity cards, keyed by card_id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export { crunchbaseAutocompleteTool } from './autocomplete'
|
||||
export { crunchbaseGetAcquisitionTool } from './get_acquisition'
|
||||
export { crunchbaseGetEntityTool } from './get_entity'
|
||||
export { crunchbaseGetEntityCardTool } from './get_entity_card'
|
||||
export { crunchbaseGetFieldsMetadataTool } from './get_fields_metadata'
|
||||
export { crunchbaseGetFundingRoundTool } from './get_funding_round'
|
||||
export { crunchbaseGetOrganizationTool } from './get_organization'
|
||||
export { crunchbaseGetPersonTool } from './get_person'
|
||||
export { crunchbaseListDeletedEntitiesTool } from './list_deleted_entities'
|
||||
export { crunchbaseSearchAcquisitionsTool } from './search_acquisitions'
|
||||
export { crunchbaseSearchEntitiesTool } from './search_entities'
|
||||
export { crunchbaseSearchFundingRoundsTool } from './search_funding_rounds'
|
||||
export { crunchbaseSearchOrganizationsTool } from './search_organizations'
|
||||
export { crunchbaseSearchPeopleTool } from './search_people'
|
||||
export type * from './types'
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { CrunchbaseEntityIdentifier } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
appendCsvParam,
|
||||
assertCollection,
|
||||
assertCollections,
|
||||
assertSingleCursor,
|
||||
CRUNCHBASE_API_BASE,
|
||||
CRUNCHBASE_DELETED_COLLECTIONS,
|
||||
clampLimit,
|
||||
crunchbaseError,
|
||||
crunchbaseHeaders,
|
||||
parseIdListParam,
|
||||
readJson,
|
||||
SEARCH_LIMIT_MAX,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
interface CrunchbaseDeletedEntity {
|
||||
deleted_at?: string
|
||||
identifier?: CrunchbaseEntityIdentifier
|
||||
}
|
||||
|
||||
interface CrunchbaseListDeletedEntitiesParams {
|
||||
apiKey: string
|
||||
collection?: string
|
||||
collectionIds?: string[] | string
|
||||
deletedAtOrder?: string
|
||||
limit?: number | string
|
||||
afterId?: string
|
||||
beforeId?: string
|
||||
}
|
||||
|
||||
interface CrunchbaseListDeletedEntitiesResponse extends ToolResponse {
|
||||
output: {
|
||||
entities: CrunchbaseDeletedEntity[]
|
||||
nextAfterId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const crunchbaseListDeletedEntitiesTool: ToolConfig<
|
||||
CrunchbaseListDeletedEntitiesParams,
|
||||
CrunchbaseListDeletedEntitiesResponse
|
||||
> = {
|
||||
id: 'crunchbase_list_deleted_entities',
|
||||
name: 'Crunchbase List Deleted Entities',
|
||||
description:
|
||||
'List entities Crunchbase has deleted, so a mirrored copy can be pruned in step with the source.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Restrict the feed to a single collection: categories, event_appearances, events, ipos, jobs, locations, organizations, ownerships, or people. Leave empty to read the feed across collections.',
|
||||
},
|
||||
collectionIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collections to include when reading the cross-collection feed, e.g. ["organizations","people"]. Ignored when a single collection is set.',
|
||||
},
|
||||
deletedAtOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order by deletion time: "asc" (default) or "desc"',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return per page',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'UUID of the last row on the current page, to fetch the next page',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'UUID of the first row on the current page, to fetch the previous page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
assertSingleCursor(params.afterId, params.beforeId)
|
||||
|
||||
const search = new URLSearchParams()
|
||||
const scoped = params.collection?.trim()
|
||||
|
||||
if (!scoped) {
|
||||
appendCsvParam(
|
||||
search,
|
||||
'collection_ids',
|
||||
assertCollections(
|
||||
parseIdListParam(params.collectionIds, 'collectionIds'),
|
||||
CRUNCHBASE_DELETED_COLLECTIONS,
|
||||
'collectionIds'
|
||||
)
|
||||
)
|
||||
}
|
||||
if (params.deletedAtOrder === 'desc' || params.deletedAtOrder === 'asc') {
|
||||
search.set('deleted_at_order', params.deletedAtOrder)
|
||||
}
|
||||
const limit = clampLimit(params.limit, SEARCH_LIMIT_MAX)
|
||||
if (limit !== undefined) search.set('limit', String(limit))
|
||||
if (params.afterId) search.set('after_id', params.afterId)
|
||||
if (params.beforeId) search.set('before_id', params.beforeId)
|
||||
|
||||
const path = scoped
|
||||
? `/deleted_entities/${assertCollection(scoped, CRUNCHBASE_DELETED_COLLECTIONS, 'collection')}`
|
||||
: '/deleted_entities'
|
||||
const qs = search.toString()
|
||||
return `${CRUNCHBASE_API_BASE}${path}${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => crunchbaseHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
const data = await readJson<{ entities?: CrunchbaseDeletedEntity[] }>(response)
|
||||
const entities = Array.isArray(data.entities) ? data.entities : []
|
||||
const last = entities[entities.length - 1]
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entities,
|
||||
nextAfterId: last?.identifier?.uuid ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Deleted entities as [{deleted_at, identifier: {uuid, value, permalink, entity_def_id}}]',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { CrunchbaseSearchParams, CrunchbaseSearchResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildSearchBody,
|
||||
CRUNCHBASE_API_BASE,
|
||||
crunchbaseJsonHeaders,
|
||||
DEFAULT_ACQUISITION_FIELD_IDS,
|
||||
transformSearchResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseSearchAcquisitionsTool: ToolConfig<
|
||||
CrunchbaseSearchParams,
|
||||
CrunchbaseSearchResponse
|
||||
> = {
|
||||
id: 'crunchbase_search_acquisitions',
|
||||
name: 'Crunchbase Search Acquisitions',
|
||||
description:
|
||||
'Search Crunchbase acquisitions with filter predicates on announced date, price, acquisition type, and the companies involved.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
query: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter predicates, combined with AND. Array of {type:"predicate", field_id, operator_id, values}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: [{"type":"predicate","field_id":"announced_on","operator_id":"gte","values":["2026-01-01"]}]',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Acquisition fields to return as columns, e.g. ["identifier","acquiree_identifier","acquirer_identifier","price"]. Defaults to identifier, acquiree_identifier, acquirer_identifier, announced_on, completed_on, price, acquisition_type, status, terms, short_description, permalink.',
|
||||
},
|
||||
order: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort clauses, e.g. [{"field_id":"announced_on","sort":"desc","nulls":"last"}]. Sort is "asc" or "desc".',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return, 1-1000 (default 100)',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId.',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: `${CRUNCHBASE_API_BASE}/searches/acquisitions`,
|
||||
method: 'POST',
|
||||
headers: (params) => crunchbaseJsonHeaders(params.apiKey),
|
||||
body: (params) => buildSearchBody(params, DEFAULT_ACQUISITION_FIELD_IDS),
|
||||
},
|
||||
|
||||
transformResponse: transformSearchResponse,
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
nullable: true,
|
||||
description: 'Total number of acquisitions matching the query',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Matching acquisitions as [{uuid, properties}], where properties holds the requested field_ids',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { CrunchbaseSearchParams, CrunchbaseSearchResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
assertCollection,
|
||||
buildSearchBody,
|
||||
CRUNCHBASE_API_BASE,
|
||||
CRUNCHBASE_COLLECTIONS,
|
||||
crunchbaseJsonHeaders,
|
||||
transformSearchResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CrunchbaseSearchEntitiesParams extends CrunchbaseSearchParams {
|
||||
collection: string
|
||||
}
|
||||
|
||||
export const crunchbaseSearchEntitiesTool: ToolConfig<
|
||||
CrunchbaseSearchEntitiesParams,
|
||||
CrunchbaseSearchResponse
|
||||
> = {
|
||||
id: 'crunchbase_search_entities',
|
||||
name: 'Crunchbase Search Entities',
|
||||
description:
|
||||
'Search any Crunchbase collection — events, jobs, ipos, funds, investments, press references, layoffs, insights, predictions, and more — with filter predicates.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Collection to search. One of: acquisition_predictions, acquisitions, addresses, awards, categories, category_groups, closure_predictions, current_valuation_estimates, degrees, diversity_spotlights, event_appearances, events, funding_predictions, funding_rounds, funds, growth_insights, growth_predictions, investments, investor_insights, investor_matches, ipo_predictions, ipos, jobs, key_employee_changes, layoff_predictions, layoffs, legal_proceedings, locations, market_insight_reasons, market_insights, micro_categories, org_similarities, organizations, ownerships, partnership_announcements, people, press_references, principals, product_launches, product_similarities, products, remain_private_predictions, research_insights.',
|
||||
},
|
||||
query: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter predicates, combined with AND. Array of {type:"predicate", field_id, operator_id, values}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates.',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields to return as columns for the chosen collection, e.g. ["identifier","short_description"]. Required — the valid ids differ per collection; list them with the Get Fields Metadata operation.',
|
||||
},
|
||||
order: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort clauses, e.g. [{"field_id":"updated_at","sort":"desc","nulls":"last"}]. Sort is "asc" or "desc".',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return, 1-1000 (default 100)',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId.',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${CRUNCHBASE_API_BASE}/searches/${assertCollection(params.collection, CRUNCHBASE_COLLECTIONS, 'collection')}`,
|
||||
method: 'POST',
|
||||
headers: (params) => crunchbaseJsonHeaders(params.apiKey),
|
||||
body: (params) => buildSearchBody(params),
|
||||
},
|
||||
|
||||
transformResponse: transformSearchResponse,
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
nullable: true,
|
||||
description: 'Total number of entities matching the query',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Matching entities as [{uuid, properties}], where properties holds the requested field_ids',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { CrunchbaseSearchParams, CrunchbaseSearchResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildSearchBody,
|
||||
CRUNCHBASE_API_BASE,
|
||||
crunchbaseJsonHeaders,
|
||||
DEFAULT_FUNDING_ROUND_FIELD_IDS,
|
||||
transformSearchResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseSearchFundingRoundsTool: ToolConfig<
|
||||
CrunchbaseSearchParams,
|
||||
CrunchbaseSearchResponse
|
||||
> = {
|
||||
id: 'crunchbase_search_funding_rounds',
|
||||
name: 'Crunchbase Search Funding Rounds',
|
||||
description:
|
||||
'Search Crunchbase funding rounds with filter predicates on announced date, investment type, amount raised, and investors.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
query: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter predicates, combined with AND. Array of {type:"predicate", field_id, operator_id, values}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: [{"type":"predicate","field_id":"announced_on","operator_id":"gte","values":["2026-01-01"]}]',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Funding round fields to return as columns, e.g. ["identifier","announced_on","money_raised","investor_identifiers"]. Defaults to identifier, announced_on, investment_type, investment_stage, money_raised, funded_organization_identifier, investor_identifiers, lead_investor_identifiers, num_investors, short_description, permalink.',
|
||||
},
|
||||
order: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort clauses, e.g. [{"field_id":"announced_on","sort":"desc","nulls":"last"}]. Sort is "asc" or "desc".',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return, 1-1000 (default 100)',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId.',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: `${CRUNCHBASE_API_BASE}/searches/funding_rounds`,
|
||||
method: 'POST',
|
||||
headers: (params) => crunchbaseJsonHeaders(params.apiKey),
|
||||
body: (params) => buildSearchBody(params, DEFAULT_FUNDING_ROUND_FIELD_IDS),
|
||||
},
|
||||
|
||||
transformResponse: transformSearchResponse,
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
nullable: true,
|
||||
description: 'Total number of funding rounds matching the query',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Matching funding rounds as [{uuid, properties}], where properties holds the requested field_ids',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { CrunchbaseSearchParams, CrunchbaseSearchResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildSearchBody,
|
||||
CRUNCHBASE_API_BASE,
|
||||
crunchbaseJsonHeaders,
|
||||
DEFAULT_ORGANIZATION_FIELD_IDS,
|
||||
transformSearchResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseSearchOrganizationsTool: ToolConfig<
|
||||
CrunchbaseSearchParams,
|
||||
CrunchbaseSearchResponse
|
||||
> = {
|
||||
id: 'crunchbase_search_organizations',
|
||||
name: 'Crunchbase Search Organizations',
|
||||
description:
|
||||
'Search Crunchbase companies, investors, and schools with filter predicates on funding, headcount, location, category, and rank.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
query: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter predicates, combined with AND. Array of {type:"predicate", field_id, operator_id, values}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: [{"type":"predicate","field_id":"categories","operator_id":"includes","values":["biotechnology"]}]',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Organization fields to return as columns, e.g. ["identifier","name","founded_on","categories"]. Defaults to identifier, name, short_description, website_url, linkedin, location_identifiers, categories, founded_on, num_employees_enum, operating_status, rank_org, permalink.',
|
||||
},
|
||||
order: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort clauses, e.g. [{"field_id":"rank_org","sort":"asc","nulls":"last"}]. Sort is "asc" or "desc".',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return, 1-1000 (default 100)',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId.',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: `${CRUNCHBASE_API_BASE}/searches/organizations`,
|
||||
method: 'POST',
|
||||
headers: (params) => crunchbaseJsonHeaders(params.apiKey),
|
||||
body: (params) => buildSearchBody(params, DEFAULT_ORGANIZATION_FIELD_IDS),
|
||||
},
|
||||
|
||||
transformResponse: transformSearchResponse,
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
nullable: true,
|
||||
description: 'Total number of organizations matching the query',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Matching organizations as [{uuid, properties}], where properties holds the requested field_ids',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { CrunchbaseSearchParams, CrunchbaseSearchResponse } from '@/tools/crunchbase/types'
|
||||
import {
|
||||
buildSearchBody,
|
||||
CRUNCHBASE_API_BASE,
|
||||
crunchbaseJsonHeaders,
|
||||
DEFAULT_PERSON_FIELD_IDS,
|
||||
transformSearchResponse,
|
||||
} from '@/tools/crunchbase/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const crunchbaseSearchPeopleTool: ToolConfig<
|
||||
CrunchbaseSearchParams,
|
||||
CrunchbaseSearchResponse
|
||||
> = {
|
||||
id: 'crunchbase_search_people',
|
||||
name: 'Crunchbase Search People',
|
||||
description:
|
||||
'Search Crunchbase people — founders, executives, and investors — with filter predicates on job title, organization, location, and rank.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.CRUNCHBASE_ERRORS,
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Crunchbase API key, sent as the X-cb-user-key header',
|
||||
},
|
||||
query: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter predicates, combined with AND. Array of {type:"predicate", field_id, operator_id, values}. Operators: blank, eq, not_eq, gt, gte, lt, lte, starts, contains, not_contains, between, includes, not_includes, includes_all, not_includes_all, domain_eq, not_domain_eq, domain_blank, domain_includes, not_domain_includes. Max 25 predicates. Example: [{"type":"predicate","field_id":"primary_job_title","operator_id":"contains","values":["Founder"]}]',
|
||||
},
|
||||
fieldIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Person fields to return as columns, e.g. ["identifier","name","primary_job_title","primary_organization"]. Defaults to identifier, name, first_name, last_name, primary_job_title, primary_organization, short_description, location_identifiers, linkedin, rank_person, permalink.',
|
||||
},
|
||||
order: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort clauses, e.g. [{"field_id":"rank_person","sort":"asc","nulls":"last"}]. Sort is "asc" or "desc".',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return, 1-1000 (default 100)',
|
||||
},
|
||||
afterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the last entity on the current page, to fetch the next page. Cannot be combined with beforeId.',
|
||||
},
|
||||
beforeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UUID of the first entity on the current page, to fetch the previous page. Cannot be combined with afterId.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: `${CRUNCHBASE_API_BASE}/searches/people`,
|
||||
method: 'POST',
|
||||
headers: (params) => crunchbaseJsonHeaders(params.apiKey),
|
||||
body: (params) => buildSearchBody(params, DEFAULT_PERSON_FIELD_IDS),
|
||||
},
|
||||
|
||||
transformResponse: transformSearchResponse,
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
nullable: true,
|
||||
description: 'Total number of people matching the query',
|
||||
},
|
||||
entities: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Matching people as [{uuid, properties}], where properties holds the requested field_ids',
|
||||
},
|
||||
nextAfterId: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'UUID of the last row, to pass as afterId for the next page',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Every entity in Crunchbase carries an identifier object rather than a bare id.
|
||||
* Shared by `identifier`, `categories`, `investor_identifiers`, and friends.
|
||||
*/
|
||||
export interface CrunchbaseEntityIdentifier {
|
||||
uuid: string
|
||||
entity_def_id: string
|
||||
value?: string
|
||||
permalink?: string
|
||||
image_id?: string
|
||||
}
|
||||
|
||||
/** Location identifiers add a `location_type` on top of the entity identifier. */
|
||||
export interface CrunchbaseLocationIdentifier extends CrunchbaseEntityIdentifier {
|
||||
location_type?: string
|
||||
}
|
||||
|
||||
/** A date whose known precision may be coarser than a day. */
|
||||
export interface CrunchbaseDateWithPrecision {
|
||||
precision: 'none' | 'year' | 'month' | 'day'
|
||||
value?: string
|
||||
}
|
||||
|
||||
/** A monetary amount, normalized to USD alongside its native currency. */
|
||||
export interface CrunchbaseMoney {
|
||||
currency: string
|
||||
value: number
|
||||
value_usd?: number
|
||||
}
|
||||
|
||||
/** A url paired with optional display text. */
|
||||
export interface CrunchbaseLink {
|
||||
value?: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity properties are shaped by the requested `field_ids`, so the payload is
|
||||
* a dynamic bag rather than a fixed record.
|
||||
*/
|
||||
export type CrunchbaseProperties = Record<string, unknown>
|
||||
|
||||
/** One row of a search result: the entity uuid plus its requested properties. */
|
||||
export interface CrunchbaseSearchEntity {
|
||||
uuid?: string
|
||||
properties?: CrunchbaseProperties
|
||||
}
|
||||
|
||||
/** One autocomplete suggestion. */
|
||||
export interface CrunchbaseAutocompleteEntity {
|
||||
identifier: CrunchbaseEntityIdentifier
|
||||
facet_ids?: string[]
|
||||
short_description?: string
|
||||
}
|
||||
|
||||
/** A single `query` filter. Crunchbase combines predicates with AND only. */
|
||||
export interface CrunchbasePredicate {
|
||||
type: 'predicate'
|
||||
field_id: string
|
||||
operator_id: string
|
||||
values?: Array<string | number | boolean>
|
||||
}
|
||||
|
||||
/** A single `order` clause. */
|
||||
export interface CrunchbaseOrder {
|
||||
field_id: string
|
||||
sort: 'asc' | 'desc'
|
||||
nulls?: 'first' | 'last'
|
||||
}
|
||||
|
||||
interface CrunchbaseBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface CrunchbaseSearchParams extends CrunchbaseBaseParams {
|
||||
query?: CrunchbasePredicate[] | string
|
||||
fieldIds?: string[] | string
|
||||
order?: CrunchbaseOrder[] | string
|
||||
limit?: number | string
|
||||
afterId?: string
|
||||
beforeId?: string
|
||||
}
|
||||
|
||||
export interface CrunchbaseEntityParams extends CrunchbaseBaseParams {
|
||||
entityId: string
|
||||
fieldIds?: string[] | string
|
||||
cardIds?: string[] | string
|
||||
}
|
||||
|
||||
export interface CrunchbaseAutocompleteParams extends CrunchbaseBaseParams {
|
||||
query: string
|
||||
collectionIds?: string[] | string
|
||||
limit?: number | string
|
||||
}
|
||||
|
||||
export interface CrunchbaseSearchResponse extends ToolResponse {
|
||||
output: {
|
||||
count: number | null
|
||||
entities: CrunchbaseSearchEntity[]
|
||||
nextAfterId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface CrunchbaseEntityResponse extends ToolResponse {
|
||||
output: {
|
||||
uuid: string | null
|
||||
name: string | null
|
||||
permalink: string | null
|
||||
properties: CrunchbaseProperties
|
||||
cards: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface CrunchbaseAutocompleteResponse extends ToolResponse {
|
||||
output: {
|
||||
entities: CrunchbaseAutocompleteEntity[]
|
||||
}
|
||||
}
|
||||
|
||||
export type CrunchbaseResponse =
|
||||
| CrunchbaseSearchResponse
|
||||
| CrunchbaseEntityResponse
|
||||
| CrunchbaseAutocompleteResponse
|
||||
@@ -0,0 +1,595 @@
|
||||
import type {
|
||||
CrunchbaseEntityIdentifier,
|
||||
CrunchbaseEntityParams,
|
||||
CrunchbaseEntityResponse,
|
||||
CrunchbaseOrder,
|
||||
CrunchbasePredicate,
|
||||
CrunchbaseProperties,
|
||||
CrunchbaseSearchEntity,
|
||||
CrunchbaseSearchParams,
|
||||
CrunchbaseSearchResponse,
|
||||
} from '@/tools/crunchbase/types'
|
||||
|
||||
/** Crunchbase API v4 origin, as declared by every published OpenAPI document. */
|
||||
export const CRUNCHBASE_API_ROOT = 'https://api.crunchbase.com/v4'
|
||||
|
||||
/** Entity, search, autocomplete, and deleted-entity endpoints hang off `/data`. */
|
||||
export const CRUNCHBASE_API_BASE = `${CRUNCHBASE_API_ROOT}/data`
|
||||
|
||||
/**
|
||||
* Every collection exposed by `/data/searches/{collection}` and
|
||||
* `/data/entities/{collection}/{entity_id}`.
|
||||
*
|
||||
* Both endpoint families cover the same 43 collections. Which of them answer
|
||||
* depends on the license: Firmographic, Core Financials, Advanced Financials,
|
||||
* Insights Only, and Predictions & Insights each publish a subset.
|
||||
*/
|
||||
export const CRUNCHBASE_COLLECTIONS = [
|
||||
'acquisition_predictions',
|
||||
'acquisitions',
|
||||
'addresses',
|
||||
'awards',
|
||||
'categories',
|
||||
'category_groups',
|
||||
'closure_predictions',
|
||||
'current_valuation_estimates',
|
||||
'degrees',
|
||||
'diversity_spotlights',
|
||||
'event_appearances',
|
||||
'events',
|
||||
'funding_predictions',
|
||||
'funding_rounds',
|
||||
'funds',
|
||||
'growth_insights',
|
||||
'growth_predictions',
|
||||
'investments',
|
||||
'investor_insights',
|
||||
'investor_matches',
|
||||
'ipo_predictions',
|
||||
'ipos',
|
||||
'jobs',
|
||||
'key_employee_changes',
|
||||
'layoff_predictions',
|
||||
'layoffs',
|
||||
'legal_proceedings',
|
||||
'locations',
|
||||
'market_insight_reasons',
|
||||
'market_insights',
|
||||
'micro_categories',
|
||||
'org_similarities',
|
||||
'organizations',
|
||||
'ownerships',
|
||||
'partnership_announcements',
|
||||
'people',
|
||||
'press_references',
|
||||
'principals',
|
||||
'product_launches',
|
||||
'product_similarities',
|
||||
'products',
|
||||
'remain_private_predictions',
|
||||
'research_insights',
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Collections that publish a single-card endpoint.
|
||||
*
|
||||
* `/data/entities/{collection}/{entity_id}/cards/{card_id}` is the only way past
|
||||
* the 100-item cap an inline `card_ids` request is subject to.
|
||||
*/
|
||||
export const CRUNCHBASE_CARD_COLLECTIONS = [
|
||||
'acquisitions',
|
||||
'addresses',
|
||||
'categories',
|
||||
'category_groups',
|
||||
'degrees',
|
||||
'event_appearances',
|
||||
'events',
|
||||
'funding_rounds',
|
||||
'funds',
|
||||
'investments',
|
||||
'ipos',
|
||||
'jobs',
|
||||
'market_insights',
|
||||
'micro_categories',
|
||||
'organizations',
|
||||
'ownerships',
|
||||
'people',
|
||||
] as const
|
||||
|
||||
/** Collections the deleted-entity feed covers. */
|
||||
export const CRUNCHBASE_DELETED_COLLECTIONS = [
|
||||
'categories',
|
||||
'event_appearances',
|
||||
'events',
|
||||
'ipos',
|
||||
'jobs',
|
||||
'locations',
|
||||
'organizations',
|
||||
'ownerships',
|
||||
'people',
|
||||
] as const
|
||||
|
||||
/** Collections the fields-metadata export covers. */
|
||||
export const CRUNCHBASE_METADATA_COLLECTIONS = [
|
||||
'addresses',
|
||||
'categories',
|
||||
'category_groups',
|
||||
'degrees',
|
||||
'diversity_spotlights',
|
||||
'event_appearances',
|
||||
'events',
|
||||
'ipos',
|
||||
'jobs',
|
||||
'locations',
|
||||
'organizations',
|
||||
'ownerships',
|
||||
'people',
|
||||
'principals',
|
||||
] as const
|
||||
|
||||
/** Collections the autocomplete endpoint can be constrained to. */
|
||||
export const CRUNCHBASE_AUTOCOMPLETE_COLLECTIONS = [
|
||||
'addresses',
|
||||
'categories',
|
||||
'category_groups',
|
||||
'degrees',
|
||||
'diversity_spotlights',
|
||||
'event_appearances',
|
||||
'events',
|
||||
'ipos',
|
||||
'jobs',
|
||||
'locations',
|
||||
'organizations',
|
||||
'ownerships',
|
||||
'people',
|
||||
'principals',
|
||||
] as const
|
||||
|
||||
/** `limit` bounds the Search API documents (default 100). */
|
||||
export const SEARCH_LIMIT_MIN = 1
|
||||
export const SEARCH_LIMIT_MAX = 1000
|
||||
export const SEARCH_LIMIT_DEFAULT = 100
|
||||
|
||||
/** `limit` bounds the Autocomplete API documents (default 10). */
|
||||
export const AUTOCOMPLETE_LIMIT_MAX = 25
|
||||
|
||||
/** Crunchbase allows at most 25 predicates, each carrying at most 200 values. */
|
||||
export const MAX_PREDICATES = 25
|
||||
export const MAX_PREDICATE_VALUES = 200
|
||||
|
||||
/**
|
||||
* Default `field_ids` for the collections this integration exposes directly.
|
||||
*
|
||||
* `field_ids` is required on every Search request and narrows a lookup to the
|
||||
* identifier alone when omitted, so each of those tools falls back to a readable
|
||||
* column set. Every id below is taken from the published field enum of the
|
||||
* narrowest package that exposes its collection, so they resolve on the widest
|
||||
* range of licenses.
|
||||
*/
|
||||
export const DEFAULT_ORGANIZATION_FIELD_IDS = [
|
||||
'identifier',
|
||||
'name',
|
||||
'short_description',
|
||||
'website_url',
|
||||
'linkedin',
|
||||
'location_identifiers',
|
||||
'categories',
|
||||
'founded_on',
|
||||
'num_employees_enum',
|
||||
'operating_status',
|
||||
'rank_org',
|
||||
'permalink',
|
||||
] as const
|
||||
|
||||
export const DEFAULT_PERSON_FIELD_IDS = [
|
||||
'identifier',
|
||||
'name',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'primary_job_title',
|
||||
'primary_organization',
|
||||
'short_description',
|
||||
'location_identifiers',
|
||||
'linkedin',
|
||||
'rank_person',
|
||||
'permalink',
|
||||
] as const
|
||||
|
||||
export const DEFAULT_FUNDING_ROUND_FIELD_IDS = [
|
||||
'identifier',
|
||||
'announced_on',
|
||||
'investment_type',
|
||||
'investment_stage',
|
||||
'money_raised',
|
||||
'funded_organization_identifier',
|
||||
'investor_identifiers',
|
||||
'lead_investor_identifiers',
|
||||
'num_investors',
|
||||
'short_description',
|
||||
'permalink',
|
||||
] as const
|
||||
|
||||
export const DEFAULT_ACQUISITION_FIELD_IDS = [
|
||||
'identifier',
|
||||
'acquiree_identifier',
|
||||
'acquirer_identifier',
|
||||
'announced_on',
|
||||
'completed_on',
|
||||
'price',
|
||||
'acquisition_type',
|
||||
'status',
|
||||
'terms',
|
||||
'short_description',
|
||||
'permalink',
|
||||
] as const
|
||||
|
||||
/** Authenticates with the documented API-key header rather than a query param. */
|
||||
export function crunchbaseHeaders(apiKey: string): Record<string, string> {
|
||||
return {
|
||||
'X-cb-user-key': apiKey,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the body content type the Search API's POST requests carry. */
|
||||
export function crunchbaseJsonHeaders(apiKey: string): Record<string, string> {
|
||||
return { ...crunchbaseHeaders(apiKey), 'Content-Type': 'application/json' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a JSON-array param that may arrive already parsed.
|
||||
*
|
||||
* A block-to-block reference hands over a real array; a text field and an LLM
|
||||
* tool call both hand over a JSON string. Throwing here is deliberate — the
|
||||
* request builder is the one place the executor surfaces the failure cleanly
|
||||
* instead of silently sending a string where an array belongs.
|
||||
*/
|
||||
export function parseArrayParam<T>(value: unknown, paramName: string): T[] | undefined {
|
||||
if (value === undefined || value === null) return undefined
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim()
|
||||
if (trimmed === '') return undefined
|
||||
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(trimmed)
|
||||
} catch {
|
||||
throw new Error(`Crunchbase "${paramName}" must be a JSON array`)
|
||||
}
|
||||
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error(`Crunchbase "${paramName}" must be a JSON array`)
|
||||
}
|
||||
return parsed as T[]
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) return value as T[]
|
||||
|
||||
throw new Error(`Crunchbase "${paramName}" must be a JSON array`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a list of plain ids, which a bare comma-separated string satisfies.
|
||||
*
|
||||
* Only for flat id lists (`field_ids`, `card_ids`, `collection_ids`) — typing
|
||||
* `identifier, name` by hand is natural there, whereas a predicate or sort
|
||||
* clause is an object and a comma split would quietly produce nonsense.
|
||||
*/
|
||||
export function parseIdListParam(value: unknown, paramName: string): string[] | undefined {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim()
|
||||
if (trimmed === '') return undefined
|
||||
if (!trimmed.startsWith('[')) {
|
||||
return trimmed
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = parseArrayParam<unknown>(value, paramName)
|
||||
return parsed?.map((entry) => String(entry))
|
||||
}
|
||||
|
||||
/**
|
||||
* `limit` bound for a single card page.
|
||||
*
|
||||
* A card returns at most 100 items, and the Limit subblock is shared with Search
|
||||
* (max 1000), so a value carried over from a search would otherwise go out on
|
||||
* the wire and be rejected.
|
||||
*/
|
||||
export const CARD_LIMIT_MAX = 100
|
||||
|
||||
/**
|
||||
* Rejects the cursor pair Crunchbase documents as mutually exclusive.
|
||||
*
|
||||
* `after_id` "may not be provided simultaneously with before_id" on every paged
|
||||
* endpoint, and the block shares one After ID / Before ID pair across searches,
|
||||
* card pages, and the deleted feed — so a leftover value really can arrive here.
|
||||
*/
|
||||
export function assertSingleCursor(afterId?: string, beforeId?: string): void {
|
||||
if (afterId && beforeId) {
|
||||
throw new Error('Crunchbase accepts either "afterId" or "beforeId", not both')
|
||||
}
|
||||
}
|
||||
|
||||
/** Coerces a numeric param and clamps it into the endpoint's documented range. */
|
||||
export function clampLimit(value: unknown, max: number, fallback?: number): number | undefined {
|
||||
if (value === undefined || value === null || value === '') return fallback
|
||||
const parsed = typeof value === 'number' ? value : Number(value)
|
||||
if (!Number.isFinite(parsed)) return fallback
|
||||
return Math.min(Math.max(Math.trunc(parsed), SEARCH_LIMIT_MIN), max)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the predicate list before it leaves Sim.
|
||||
*
|
||||
* A malformed predicate comes back as a generic 400, so naming the offending
|
||||
* entry here is the difference between a fixable message and a dead end.
|
||||
*/
|
||||
export function normalizePredicates(value: unknown): CrunchbasePredicate[] {
|
||||
const predicates = parseArrayParam<Record<string, unknown>>(value, 'query') ?? []
|
||||
|
||||
if (predicates.length > MAX_PREDICATES) {
|
||||
throw new Error(`Crunchbase accepts at most ${MAX_PREDICATES} query predicates`)
|
||||
}
|
||||
|
||||
return predicates.map((predicate, index) => {
|
||||
if (typeof predicate !== 'object' || predicate === null) {
|
||||
throw new Error(`Crunchbase query predicate ${index + 1} must be an object`)
|
||||
}
|
||||
const fieldId = predicate.field_id
|
||||
const operatorId = predicate.operator_id
|
||||
if (typeof fieldId !== 'string' || fieldId === '') {
|
||||
throw new Error(`Crunchbase query predicate ${index + 1} is missing "field_id"`)
|
||||
}
|
||||
if (typeof operatorId !== 'string' || operatorId === '') {
|
||||
throw new Error(`Crunchbase query predicate ${index + 1} is missing "operator_id"`)
|
||||
}
|
||||
|
||||
const values = predicate.values
|
||||
if (Array.isArray(values) && values.length > MAX_PREDICATE_VALUES) {
|
||||
throw new Error(
|
||||
`Crunchbase query predicate ${index + 1} carries ${values.length} values; at most ${MAX_PREDICATE_VALUES} are allowed`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'predicate',
|
||||
field_id: fieldId,
|
||||
operator_id: operatorId,
|
||||
...(Array.isArray(values) ? { values: values as Array<string | number | boolean> } : {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Validates the sort clauses, which share the predicates' failure mode. */
|
||||
export function normalizeOrder(value: unknown): CrunchbaseOrder[] | undefined {
|
||||
const clauses = parseArrayParam<Record<string, unknown>>(value, 'order')
|
||||
if (!clauses?.length) return undefined
|
||||
|
||||
return clauses.map((clause, index) => {
|
||||
if (typeof clause !== 'object' || clause === null) {
|
||||
throw new Error(`Crunchbase order clause ${index + 1} must be an object`)
|
||||
}
|
||||
const fieldId = clause.field_id
|
||||
if (typeof fieldId !== 'string' || fieldId === '') {
|
||||
throw new Error(`Crunchbase order clause ${index + 1} is missing "field_id"`)
|
||||
}
|
||||
const sort = clause.sort === 'desc' ? 'desc' : 'asc'
|
||||
const nulls = clause.nulls === 'first' || clause.nulls === 'last' ? clause.nulls : undefined
|
||||
return { field_id: fieldId, sort, ...(nulls ? { nulls } : {}) }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a repeated-id query param.
|
||||
*
|
||||
* The spec declares `field_ids` and `card_ids` as `style: form, explode: false`,
|
||||
* so the wire form is one comma-separated value, not repeated keys.
|
||||
*/
|
||||
export function appendCsvParam(
|
||||
search: URLSearchParams,
|
||||
key: string,
|
||||
values: readonly string[] | undefined
|
||||
): void {
|
||||
if (!values?.length) return
|
||||
search.set(key, values.join(','))
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a failed Crunchbase response into a readable error.
|
||||
*
|
||||
* Errors come back as a JSON array — `[{"status":401,"code":"LA401","message":
|
||||
* "Unauthorized user_key"}]` — so the usual `data.message` lookup finds nothing
|
||||
* and the failure would otherwise report only its HTTP status.
|
||||
*/
|
||||
export async function crunchbaseError(response: Response): Promise<Error> {
|
||||
const raw = await response.text()
|
||||
let detail = raw.trim()
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw)
|
||||
const entries: unknown[] = Array.isArray(parsed) ? parsed : [parsed]
|
||||
const messages = entries
|
||||
.map((entry) => {
|
||||
if (typeof entry !== 'object' || entry === null) return ''
|
||||
const message = (entry as { message?: unknown }).message
|
||||
return typeof message === 'string' ? message.trim() : ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
if (messages.length > 0) detail = messages.join('; ')
|
||||
} catch {
|
||||
/* Not JSON — fall back to the raw body, which is usually a gateway page. */
|
||||
}
|
||||
|
||||
return new Error(
|
||||
`Crunchbase API error: ${response.status} ${response.statusText}${detail ? ` - ${detail}` : ''}`
|
||||
)
|
||||
}
|
||||
|
||||
/** Reads a JSON body, tolerating an empty one rather than throwing on it. */
|
||||
export async function readJson<T>(response: Response): Promise<T> {
|
||||
const raw = await response.text()
|
||||
if (raw.trim() === '') return {} as T
|
||||
return JSON.parse(raw) as T
|
||||
}
|
||||
|
||||
function asIdentifier(value: unknown): CrunchbaseEntityIdentifier | null {
|
||||
if (typeof value !== 'object' || value === null) return null
|
||||
const identifier = value as Partial<CrunchbaseEntityIdentifier>
|
||||
return typeof identifier.uuid === 'string' ? (identifier as CrunchbaseEntityIdentifier) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifts the identity of a looked-up entity out of its dynamic property bag.
|
||||
*
|
||||
* Which keys are present depends entirely on the requested `field_ids`, so each
|
||||
* value degrades to null rather than throwing.
|
||||
*/
|
||||
export function extractIdentity(properties: CrunchbaseProperties | undefined): {
|
||||
uuid: string | null
|
||||
name: string | null
|
||||
permalink: string | null
|
||||
} {
|
||||
const identifier = asIdentifier(properties?.identifier)
|
||||
const name = typeof properties?.name === 'string' ? properties.name : null
|
||||
const permalink = typeof properties?.permalink === 'string' ? properties.permalink : null
|
||||
|
||||
return {
|
||||
uuid: identifier?.uuid ?? null,
|
||||
name: name ?? identifier?.value ?? null,
|
||||
permalink: permalink ?? identifier?.permalink ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Search API body.
|
||||
*
|
||||
* Shared at runtime only — each tool still spells out its own `params` and
|
||||
* `outputs` literally, because the docs generator reads tool sources statically
|
||||
* and cannot follow a spread from this module.
|
||||
*/
|
||||
export function buildSearchBody(
|
||||
params: CrunchbaseSearchParams,
|
||||
defaultFieldIds?: readonly string[]
|
||||
): Record<string, unknown> {
|
||||
const fieldIds = parseIdListParam(params.fieldIds, 'fieldIds')
|
||||
const order = normalizeOrder(params.order)
|
||||
const limit = clampLimit(params.limit, SEARCH_LIMIT_MAX, SEARCH_LIMIT_DEFAULT)
|
||||
|
||||
assertSingleCursor(params.afterId, params.beforeId)
|
||||
|
||||
const resolvedFieldIds = fieldIds?.length ? fieldIds : [...(defaultFieldIds ?? [])]
|
||||
if (resolvedFieldIds.length === 0) {
|
||||
throw new Error(
|
||||
'Crunchbase "fieldIds" is required — the valid ids differ per collection, so there is no safe default'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
field_ids: resolvedFieldIds,
|
||||
query: normalizePredicates(params.query),
|
||||
...(order ? { order } : {}),
|
||||
...(limit !== undefined ? { limit } : {}),
|
||||
...(params.afterId ? { after_id: params.afterId } : {}),
|
||||
...(params.beforeId ? { before_id: params.beforeId } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
/** Projects a Search API response, carrying the cursor for the next page. */
|
||||
export async function transformSearchResponse(
|
||||
response: Response
|
||||
): Promise<CrunchbaseSearchResponse> {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
const data = await readJson<{ count?: number; entities?: CrunchbaseSearchEntity[] }>(response)
|
||||
const entities = Array.isArray(data.entities) ? data.entities : []
|
||||
const last = entities[entities.length - 1]
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
count: typeof data.count === 'number' ? data.count : null,
|
||||
entities,
|
||||
nextAfterId: last?.uuid ?? null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds an Entity Lookup URL from the shared `field_ids` / `card_ids` pair. */
|
||||
export function buildEntityUrl(
|
||||
collection: string,
|
||||
params: CrunchbaseEntityParams,
|
||||
defaultFieldIds?: readonly string[]
|
||||
): string {
|
||||
const entityId = params.entityId?.trim()
|
||||
if (!entityId) throw new Error('Crunchbase "entityId" (uuid or permalink) is required')
|
||||
|
||||
const fieldIds = parseIdListParam(params.fieldIds, 'fieldIds')
|
||||
const cardIds = parseIdListParam(params.cardIds, 'cardIds')
|
||||
|
||||
/* With no field_ids the API answers with its own default projection, which is
|
||||
the honest fallback for a collection this integration has no verified list
|
||||
for. */
|
||||
const search = new URLSearchParams()
|
||||
appendCsvParam(search, 'field_ids', fieldIds?.length ? fieldIds : defaultFieldIds)
|
||||
appendCsvParam(search, 'card_ids', cardIds)
|
||||
|
||||
const qs = search.toString()
|
||||
return `${CRUNCHBASE_API_BASE}/entities/${collection}/${encodeURIComponent(entityId)}${qs ? `?${qs}` : ''}`
|
||||
}
|
||||
|
||||
/** Projects an Entity Lookup response into its identity, fields, and cards. */
|
||||
export async function transformEntityResponse(
|
||||
response: Response
|
||||
): Promise<CrunchbaseEntityResponse> {
|
||||
if (!response.ok) throw await crunchbaseError(response)
|
||||
|
||||
const data = await readJson<{
|
||||
properties?: CrunchbaseProperties
|
||||
cards?: Record<string, unknown>
|
||||
}>(response)
|
||||
const properties = data.properties ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...extractIdentity(properties),
|
||||
properties,
|
||||
cards: data.cards ?? null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Rejects a collection the API does not publish before spending a round trip. */
|
||||
export function assertCollection(
|
||||
value: unknown,
|
||||
allowed: readonly string[],
|
||||
paramName: string
|
||||
): string {
|
||||
const collection = typeof value === 'string' ? value.trim() : ''
|
||||
if (!allowed.includes(collection)) {
|
||||
throw new Error(
|
||||
`Crunchbase "${paramName}" must be one of: ${allowed.join(', ')} (received "${String(value ?? '')}")`
|
||||
)
|
||||
}
|
||||
return collection
|
||||
}
|
||||
|
||||
/** Rejects any collection id the endpoint does not publish. */
|
||||
export function assertCollections(
|
||||
values: readonly string[] | undefined,
|
||||
allowed: readonly string[],
|
||||
paramName: string
|
||||
): string[] | undefined {
|
||||
if (!values?.length) return undefined
|
||||
const unknown = values.filter((value) => !allowed.includes(value))
|
||||
if (unknown.length > 0) {
|
||||
throw new Error(
|
||||
`Crunchbase "${paramName}" contains unsupported collection(s): ${unknown.join(', ')}. Allowed: ${allowed.join(', ')}`
|
||||
)
|
||||
}
|
||||
return [...values]
|
||||
}
|
||||
@@ -298,6 +298,24 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [
|
||||
return typeof attr === 'string' && attr ? `${detail} (${attr})` : detail
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'crunchbase-errors',
|
||||
description:
|
||||
'Crunchbase Data API error envelope: a top-level JSON array of {status, code, message}. Nothing else in this registry reads a bare array, so without it a rejected key or malformed predicate reports only its HTTP status',
|
||||
examples: ['Crunchbase'],
|
||||
extract: (errorInfo) => {
|
||||
const entries = Array.isArray(errorInfo?.data) ? errorInfo.data : undefined
|
||||
if (!entries?.length) return undefined
|
||||
|
||||
const messages = entries
|
||||
.map((entry: { message?: unknown }) =>
|
||||
typeof entry?.message === 'string' ? entry.message.trim() : ''
|
||||
)
|
||||
.filter(Boolean)
|
||||
|
||||
return messages.length > 0 ? messages.join('; ') : undefined
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'splunk-errors',
|
||||
description:
|
||||
@@ -403,6 +421,7 @@ export const ErrorExtractorId = {
|
||||
DYNATRACE_ERRORS: 'dynatrace-errors',
|
||||
SMARTLEAD_ERRORS: 'smartlead-errors',
|
||||
POSTHOG_ERRORS: 'posthog-errors',
|
||||
CRUNCHBASE_ERRORS: 'crunchbase-errors',
|
||||
SPLUNK_ERRORS: 'splunk-errors',
|
||||
PLAIN_TEXT_DATA: 'plain-text-data',
|
||||
HTTP_STATUS_TEXT: 'http-status-text',
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -778,6 +778,22 @@ import {
|
||||
crowdstrikeUpdateAlertsTool,
|
||||
crowdstrikeUpdateIndicatorsTool,
|
||||
} from '@/tools/crowdstrike'
|
||||
import {
|
||||
crunchbaseAutocompleteTool,
|
||||
crunchbaseGetAcquisitionTool,
|
||||
crunchbaseGetEntityCardTool,
|
||||
crunchbaseGetEntityTool,
|
||||
crunchbaseGetFieldsMetadataTool,
|
||||
crunchbaseGetFundingRoundTool,
|
||||
crunchbaseGetOrganizationTool,
|
||||
crunchbaseGetPersonTool,
|
||||
crunchbaseListDeletedEntitiesTool,
|
||||
crunchbaseSearchAcquisitionsTool,
|
||||
crunchbaseSearchEntitiesTool,
|
||||
crunchbaseSearchFundingRoundsTool,
|
||||
crunchbaseSearchOrganizationsTool,
|
||||
crunchbaseSearchPeopleTool,
|
||||
} from '@/tools/crunchbase'
|
||||
import {
|
||||
cursorAddFollowupTool,
|
||||
cursorAddFollowupV2Tool,
|
||||
@@ -6891,6 +6907,20 @@ export const tools: Record<string, ToolConfig> = {
|
||||
crowdstrike_query_vulnerabilities: crowdstrikeQueryVulnerabilitiesTool,
|
||||
crowdstrike_update_alerts: crowdstrikeUpdateAlertsTool,
|
||||
crowdstrike_update_indicators: crowdstrikeUpdateIndicatorsTool,
|
||||
crunchbase_autocomplete: crunchbaseAutocompleteTool,
|
||||
crunchbase_get_acquisition: crunchbaseGetAcquisitionTool,
|
||||
crunchbase_get_entity: crunchbaseGetEntityTool,
|
||||
crunchbase_get_entity_card: crunchbaseGetEntityCardTool,
|
||||
crunchbase_get_fields_metadata: crunchbaseGetFieldsMetadataTool,
|
||||
crunchbase_get_funding_round: crunchbaseGetFundingRoundTool,
|
||||
crunchbase_get_organization: crunchbaseGetOrganizationTool,
|
||||
crunchbase_get_person: crunchbaseGetPersonTool,
|
||||
crunchbase_list_deleted_entities: crunchbaseListDeletedEntitiesTool,
|
||||
crunchbase_search_acquisitions: crunchbaseSearchAcquisitionsTool,
|
||||
crunchbase_search_entities: crunchbaseSearchEntitiesTool,
|
||||
crunchbase_search_funding_rounds: crunchbaseSearchFundingRoundsTool,
|
||||
crunchbase_search_organizations: crunchbaseSearchOrganizationsTool,
|
||||
crunchbase_search_people: crunchbaseSearchPeopleTool,
|
||||
dynamodb_get: dynamodbGetTool,
|
||||
dynamodb_put: dynamodbPutTool,
|
||||
dynamodb_query: dynamodbQueryTool,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-08-18",
|
||||
"updatedAt": "2026-08-19",
|
||||
"integrations": [
|
||||
{
|
||||
"type": "onepassword",
|
||||
@@ -4900,6 +4900,81 @@
|
||||
"integrationType": "security",
|
||||
"tags": ["identity", "monitoring", "incident-management", "automation"]
|
||||
},
|
||||
{
|
||||
"type": "crunchbase",
|
||||
"slug": "crunchbase",
|
||||
"name": "Crunchbase",
|
||||
"description": "Search and look up companies, people, funding rounds, and acquisitions",
|
||||
"longDescription": "Integrates the Crunchbase Data API into the workflow. Search organizations, people, funding rounds, and acquisitions with filter predicates, reach the other 39 collections through the generic search and lookup operations, page a single related-entity card past its 100-item cap, autocomplete names into identifiers, follow the deleted-entity feed, and list the fields each collection publishes. Which collections and fields resolve depends on your Crunchbase license.",
|
||||
"bgColor": "#0287D1",
|
||||
"iconName": "CrunchbaseIcon",
|
||||
"docsUrl": "https://docs.sim.ai/integrations/crunchbase",
|
||||
"operations": [
|
||||
{
|
||||
"name": "Search Organizations",
|
||||
"description": "Search Crunchbase companies, investors, and schools with filter predicates on funding, headcount, location, category, and rank."
|
||||
},
|
||||
{
|
||||
"name": "Get Organization",
|
||||
"description": "Look up a single Crunchbase organization by permalink or UUID, returning the requested fields and related cards."
|
||||
},
|
||||
{
|
||||
"name": "Search People",
|
||||
"description": "Search Crunchbase people — founders, executives, and investors — with filter predicates on job title, organization, location, and rank."
|
||||
},
|
||||
{
|
||||
"name": "Get Person",
|
||||
"description": "Look up a single Crunchbase person by permalink or UUID, returning the requested fields and related cards."
|
||||
},
|
||||
{
|
||||
"name": "Search Funding Rounds",
|
||||
"description": "Search Crunchbase funding rounds with filter predicates on announced date, investment type, amount raised, and investors."
|
||||
},
|
||||
{
|
||||
"name": "Get Funding Round",
|
||||
"description": "Look up a single Crunchbase funding round by permalink or UUID, returning the requested fields and related cards."
|
||||
},
|
||||
{
|
||||
"name": "Search Acquisitions",
|
||||
"description": "Search Crunchbase acquisitions with filter predicates on announced date, price, acquisition type, and the companies involved."
|
||||
},
|
||||
{
|
||||
"name": "Get Acquisition",
|
||||
"description": "Look up a single Crunchbase acquisition by permalink or UUID, returning the requested fields and related cards."
|
||||
},
|
||||
{
|
||||
"name": "Search Any Collection",
|
||||
"description": "Search any Crunchbase collection — events, jobs, ipos, funds, investments, press references, layoffs, insights, predictions, and more — with filter predicates."
|
||||
},
|
||||
{
|
||||
"name": "Get Any Entity",
|
||||
"description": "Look up a single entity in any Crunchbase collection — events, jobs, ipos, funds, investments, press references, insights, predictions, and more — by permalink or UUID."
|
||||
},
|
||||
{
|
||||
"name": "Get Entity Card",
|
||||
"description": "Page through one related-entity card of a Crunchbase entity — an investor's investments, a company's founders, a round's investors — past the 100-item cap an inline card request returns."
|
||||
},
|
||||
{
|
||||
"name": "Autocomplete",
|
||||
"description": "Suggest Crunchbase entities matching a typed query, returning the permalinks and UUIDs the lookup and search operations take."
|
||||
},
|
||||
{
|
||||
"name": "List Deleted Entities",
|
||||
"description": "List entities Crunchbase has deleted, so a mirrored copy can be pruned in step with the source."
|
||||
},
|
||||
{
|
||||
"name": "Get Fields Metadata",
|
||||
"description": "List the field ids, types, and descriptions each Crunchbase collection publishes, which is how the field_ids and query predicates of the other operations are discovered."
|
||||
}
|
||||
],
|
||||
"operationCount": 14,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
"category": "tools",
|
||||
"integrationType": "sales",
|
||||
"tags": ["enrichment", "data-analytics"]
|
||||
},
|
||||
{
|
||||
"type": "cursor_v2",
|
||||
"slug": "cursor",
|
||||
|
||||
Reference in New Issue
Block a user