All docs

OpenSearch (Raw Data Access)

Structured search, count, aggregate, and bulk ID resolution against the graph8 mashup OpenSearch indices

These endpoints provide generic, read-only, structured access to the graph8 mashup OpenSearch indices. They back the g8_opensearch_* MCP tool family. Use them when the higher-level CRM or prospecting endpoints (/contacts, /companies, /enrichment/*, /search/contacts) don’t expose the field you need to filter on.

The search, count, and aggregate routes require an active subscription (same gate as /search/contacts and /enrichment/enrich). The resolve route does not.


POST /opensearch/search

Structured search across a whitelisted set of mashup indices. The request body declares filters, fields, sort, and limit as typed inputs — the handler translates them into a safe bool/term OpenSearch query. Raw OpenSearch DSL is not accepted.

Request Body

FieldTypeRequiredDefaultDescription
indexstringYesIndex to search. One of: mashup_contacts, mashup_companies, hem2contact, ip2company
filtersobjectNoDict of field name to value. A scalar value becomes a term filter; a list becomes a terms (OR) filter. Fields must be RAW UPPERCASE index names.
fieldsarrayNoList of RAW field names to project from _source. Omit to return all fields.
sortarrayNoList of {"FIELD_NAME": "asc"} or {"FIELD_NAME": "desc"} sort objects.
limitintegerNo50Number of hits to return (1–200).
cursorarrayNonullsearch_after token from a prior response — pass to retrieve the next page.
track_total_hitsinteger (0–1000000)NonullExact-count ceiling for the total field (e.g. 100000). By default, OpenSearch caps total at 10000 for any larger result set. Pass a value in the range 0–1000000 to get an exact count up to that ceiling in the same round trip. Does not lift pagination depth — keep paging with cursor. For a count with no row payload, use /opensearch/count instead (never capped).

Example

cURL

curl -X POST "https://be.graph8.com/api/v1/opensearch/search" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "index": "mashup_contacts",
    "filters": {
      "COMPANY_DOMAIN": "acme.com",
      "COMPANY_INDUSTRY": ["SaaS", "Fintech"]
    },
    "fields": ["COMPANY_DOMAIN", "CONTACT_ID"],
    "sort": [{ "COMPANY_REVENUE": "desc" }],
    "limit": 50,
    "cursor": null,
    "track_total_hits": null
  }'

Response

{
  "data": {
    "index": "mashup_contacts",
    "total": 1,
    "hits": [
      {
        "id": "abc",
        "score": 1.5,
        "source": { "COMPANY_DOMAIN": "acme.com" },
        "sort": [42]
      }
    ],
    "cursor": [42],
    "took_ms": 7
  }
}
FieldDescription
indexThe index that was searched
totalHit count, capped at 10000 by default. Pass track_total_hits for a higher exact-count ceiling.
hitsArray of matching documents. Each has id, score, source (projected fields), and sort (search_after token for this row).
cursorPass as cursor on the next request to retrieve the following page. null when there are no more results.
took_msOpenSearch query duration in milliseconds

Count

POST /opensearch/count

Exact document count for a whitelisted index using the native OpenSearch _count API — not subject to the 10000 track_total_hits ceiling. Uses the same index and filters shape as search; paging fields are intentionally absent because a count returns no rows. This is the correct call for TAM cell sizing.

Request Body

FieldTypeRequiredDescription
indexstringYesIndex to count. Same enum as search: mashup_contacts, mashup_companies, hem2contact, ip2company
filtersobjectNoSame semantics as search — scalar value becomes term, list becomes terms (OR). Fields must be RAW UPPERCASE index names.

Example

cURL

curl -X POST "https://be.graph8.com/api/v1/opensearch/count" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "index": "mashup_companies",
    "filters": {
      "COMPANY_NAICS": ["3311", "3311.00000"],
      "COMPANY_COUNTRY": ["US", "United States"]
    }
  }'

Response

{ "data": { "index": "mashup_companies", "count": 17842 } }

Aggregate

POST /opensearch/aggregate

Generic, allowlist-bounded GROUP BY — the TAM matrix-walk primitive. Translates group_by into nested OpenSearch terms aggregations with metric sub-aggregations, then applies having, sort, and limit to the flattened groups.

Request Body

FieldTypeRequiredDefaultDescription
indexstringYesIndex to aggregate. Same enum as search.
filtersobjectNoPre-aggregation filters. Same semantics as search. Applied before grouping.
group_byarrayYesList of 1–3 RAW UPPERCASE keyword fields to group by (e.g. ["COMPANY_STATE", "COMPANY_EMPLOYEE_COUNT"]). Capped at 3 dimensions.
aggarrayNo[{"function": "count"}]List of metric aggregation objects. Defaults to a single document count. See functions table below.
havingobjectNoPost-aggregation filter by metric alias. Operators: eq, gte, lte, gt, lt. Example: {"count": {"gte": 50}}.
sortarrayNoSort groups by a metric alias. Example: [{"count": "desc"}].
limitintegerNo100Maximum number of groups to return (1–1000). total_groups reflects the count of matching groups before this slice.

agg object fields

FieldTypeRequiredDescription
functionstringYesAggregation function. One of: count, uniqExact, sum, avg, min, max. count uses the bucket document count and does not require a field. All others require a numeric field.
fieldstringConditionalRAW UPPERCASE field name to aggregate. Required for all functions except count.
aliasstringNoName to use for this metric in having, sort, and response output. Defaults to the function name.

Example

cURL

curl -X POST "https://be.graph8.com/api/v1/opensearch/aggregate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "index": "mashup_companies",
    "filters": {
      "COMPANY_NAICS": ["3311", "3311.00000"]
    },
    "group_by": ["COMPANY_STATE", "COMPANY_EMPLOYEE_COUNT"],
    "agg": [
      { "function": "count" },
      { "function": "uniqExact", "field": "COMPANY_DOMAIN", "alias": "unique_domains" }
    ],
    "having": { "count": { "gte": 50 } },
    "sort": [{ "count": "desc" }],
    "limit": 500
  }'

Response

{
  "data": {
    "index": "mashup_companies",
    "groups": [
      {
        "key": { "COMPANY_STATE": "OH", "COMPANY_EMPLOYEE_COUNT": "11-50" },
        "agg": { "count": 1247, "unique_domains": 1184 }
      }
    ],
    "total_groups": 312,
    "took_ms": 84
  }
}
FieldDescription
indexThe index that was aggregated
groupsArray of group objects. Each has key (the group-by field values) and agg (metric values keyed by alias).
total_groupsCount of groups matching having before the limit slice.
took_msOpenSearch query duration in milliseconds

Resolve

POST /opensearch/resolve

Bulk HEM-to-contact-ID or IP-to-company-ID resolution, optimised for batches up to 500. The response includes every input as a key — null for misses — so callers can iterate results without re-querying.

This endpoint does not require an active subscription.

Request Body

FieldTypeRequiredDescription
kindstringYesResolution type. One of: hem_to_contact, ip_to_company
idsarrayYesList of 1–500 string identifiers to resolve. For hem_to_contact, pass SHA-256 hashed email values. For ip_to_company, pass IP address strings.

Example

cURL

curl -X POST "https://be.graph8.com/api/v1/opensearch/resolve" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "hem_to_contact",
    "ids": ["sha256hash1", "sha256hash2"]
  }'

Response

{
  "data": {
    "kind": "hem_to_contact",
    "results": {
      "sha256hash1": "C1",
      "sha256hash2": null
    },
    "matched": 1,
    "requested": 2
  }
}
FieldDescription
kindThe resolution type that was requested
resultsObject keyed by every input identifier. Value is the resolved ID string on a hit, or null on a miss.
matchedNumber of inputs that resolved to an ID
requestedTotal number of input identifiers submitted
Build with graph8