Universal Search API
Overview
GET /api/v1/search powers a single “search bar” experience: one query hits Elasticsearch (Searchkick) for each visible record type and returns a small, flat row per hit. Results are always intersected with Pundit policy scopes so Elasticsearch never bypasses authorization.
When to use it
- Ambiguous intent — brand name, email fragment, or domain could be an advertiser, deal, contact, or publisher.
- MCP / agents — the curated
universal_searchtool maps directly to this endpoint (first tool intools/list). - Follow-ups — after you pick a row, call the narrower tool (
get_advertiser,get_deal, etc.) for full detail.
Endpoint
HTTP
GET /api/v1/search
Requires X-Api-Key (or supported bearer / legacy query param per API key docs).
Record types
Type slugs for types (subset only):
advertisers, publishers, agencies, deals, contacts, users
The users bucket is only returned for admin callers; non-admins never see it. contacts requires a non–free account with publisher (or admin).
Model Context Protocol (MCP)
MCP clients should call the universal_search tool (same query params as REST). The executor sends GET /api/v1/search with your API key. Arguments query and search are merged into q when q is omitted.
See MCP server documentation for session setup. Example tools/call payload:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "universal_search",
"arguments": {
"q": "nike",
"per_type_limit": 5,
"types": ["advertisers", "deals"]
}
}
}
API Playground
Try live JSON with your API key. MCP JSON-RPC examples use POST /mcp with the same arguments inside tools/call.
Sign in to enable live testing with your API key.
| Action | Method | Endpoint | Description |
|---|---|---|---|
| Universal search | GET |
/api/v1/search?q=... |
Omnibox query; add types=advertisers,deals and per_type_limit as needed. |
| MCP tool call | POST |
/mcp |
tools/call with name universal_search and arguments { q, types?, per_type_limit? }. |
GET /api/v1/search?q=demo&per_type_limit=5
v1
Baseline omnibox call — adjust q for your data.
GET /api/v1/search?q=brand&types=advertisers,deals&per_type_limit=3
v1
Restrict to advertisers and deals only with a low per-type cap.
POST /mcp — tools/call universal_search
MCP
Same search via MCP; types sent as a JSON array in the tool body.
Query parameters
| Parameter | Required | Description |
|---|---|---|
q |
Yes | Search string. Whitespace-only values return 400 MISSING_PARAMETER. |
types |
No | Comma-separated type slugs (e.g. advertisers,deals) or repeated array-style params. Invalid slugs are ignored. |
per_type_limit |
No | Max hits per type (default 10, max 50). |
Response shape
Successful 200 JSON includes query, results (one key per type with data and total), and meta with per_type_limit, resolved types, and searchkick_available.
{
"query": "nike",
"results": {
"advertisers": { "data": [ { "id": 1, "type": "advertiser", "title": "…" } ], "total": 1 }
},
"meta": {
"per_type_limit": 10,
"types": ["advertisers"],
"searchkick_available": true
}
}
Errors
Missing q returns 400 with MISSING_PARAMETER (standard API error envelope). When Searchkick is unavailable, the API still returns 200 with empty data arrays and meta.searchkick_available: false so clients can degrade gracefully.