Hawksearch Analytics API
A REST API that gives you direct access to your Hawksearch analytics data — search events, clicks, sales, and recommendation interactions. Build custom dashboards, feed data into your BI tools, or power site-level reporting with a single integration.
Endpoints
API Keys
Every request must include your API key in the x-api-key header. Your key is scoped to your account — it automatically restricts results to your site's data only.
POST / HTTP/1.1
Host: api.analytics.hawksearch.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Base URL
https://api.analytics.hawksearch.com
All endpoints are relative to this base URL. The API is HTTPS-only.
Data Model
All data lives in a single summary table. Each row has an event_type that determines which fields are populated. Always filter on event_type when querying fields specific to one row type.
| event_type | Represents | Key fields |
|---|---|---|
| search | A search request. Includes click details if the search led to a click. | keyword, no_of_results, visit flags |
| cart | An item added to cart, linked to the originating search. | item_id, item_price, item_quantity |
| sale | A purchased item, linked to the originating search. | item_total, order_number, order_total |
| rec_click | A click on a recommended item. Carries sale fields when the click led to a purchase. | widget_id, widget_name, item_total |
search rows (had_click, had_cart, had_sale, had_rec_click) reflect what happened across the entire session — not just the individual search row.
Field Reference
Use GET / to retrieve the live schema. The tables below document the most commonly used fields.
Search fields
| Field | Type | Description |
|---|---|---|
event_date | date | Date of the search |
keyword | string | Search term entered by the user |
tracking_id | string | Unique ID for a single search request |
visit_id | string | Session ID — shared across searches in one visit |
visitor_id | string | Browser/device identifier |
no_of_results | number | Results returned for this search |
engine_id / engine_name | string | Search engine identifier and display name |
event_year / event_month | number | Partition keys — include in filters for best performance |
Visit-level flags (on search rows)
| Field | Description |
|---|---|
had_click | This search had at least one click |
had_cart | This visit included an add-to-cart |
had_sale | This visit included a purchase |
had_rec_impression | This visit had a recommendation impression |
had_rec_click | This visit had a recommendation click |
Item fields (cart, sale, rec_click rows)
| Field | Type | Description |
|---|---|---|
item_id / item_name | string | Item identifier and title |
item_quantity | number | Quantity |
item_price | number | Unit price |
item_total | number | Line total. Populated on sale rows and attributed rec_click rows. |
order_number | string | Order identifier |
order_total | number | Total order value |
Recommendation fields (rec_click rows)
| Field | Type | Description |
|---|---|---|
widget_id | string | Recommendation widget identifier |
widget_name | string | Recommendation widget display name |
Ad-hoc Queries
Send a POST / with a structured query to pull any combination of fields from your data. Specify dimensions (what to group by), measures (what to aggregate), and filters (what to include).
dimensions / measures / filters pattern will feel immediately familiar. This is standard practice in the analytics engineering community for abstracting SQL behind a portable, declarative query layer.
{
"dimensions": ["keyword"],
"measures": [
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" },
{ "field": "item_quantity","fn": "sum", "alias": "units_sold" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["sale"] },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_month"] }
],
"order": [{ "field": "revenue", "dir": "desc" }],
"limit": 25
}
Request fields
| Field | Type | Default | Description |
|---|---|---|---|
query | string | — | Natural language question. When set, structured fields are ignored. |
dimensions | string[] | — | Fields to select and group by |
measures | object[] | — | Aggregations to compute |
filters | object[] | — | Row-level conditions (WHERE). ANDed together. |
having | object[] | — | Post-aggregation conditions (HAVING). Member must be a measure alias. |
order | object[] | — | Sort order |
page | number | 1 | Page number (1-based) |
limit | number | 100 | Rows per page (max 1000) |
dryRun | boolean | false | Return generated SQL without executing |
queryExecutionId | string | — | Re-fetch a prior cached result (valid 24 h) |
Natural Language
Set query to a plain English question instead of building the structured fields manually. The API interprets it and runs the appropriate query.
{ "query": "Top 10 keywords by revenue last month" }
Filters
Each filter object has member (the field), operator, and values. Multiple filters are combined with AND.
Date range shortcuts
// Shortcut
{ "member": "event_date", "operator": "inDateRange", "values": ["last_30_days"] }
// Explicit range
{ "member": "event_date", "operator": "inDateRange", "values": ["2026-06-01", "2026-06-30"] }
// Null check
{ "member": "widget_id", "operator": "set" }
Measures
Each measure object defines one aggregation. When measures are present, dimensions automatically become the GROUP BY.
| Property | Required | Description |
|---|---|---|
field | Yes | Field to aggregate. Use "*" with count for a row count. |
fn | Yes | count, count_distinct, sum, avg, min, max |
alias | No | Column name in the response. Auto-generated if omitted. |
[
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" },
{ "field": "*", "fn": "count", "alias": "row_count" }
]
Pre-built Reports
Report endpoints run fixed, pre-optimised queries — faster than ad-hoc queries and require no field knowledge. Send a date range and receive paginated results.
{
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"page": 1,
"limit": 100
}
| Endpoint | What it returns |
|---|---|
/daily-searches | Daily search count by engine and group |
/top-queries | Top keywords by search volume |
/poor-results | Keywords returning 10 or fewer results |
/spelling-suggestion | Daily spelling correction counts |
/daily-sales | Daily funnel — searches, clicks, carts, orders, revenue |
/sales-by-product | Revenue and orders by product and keyword |
/recommendations | Widget impressions, clicks, add-to-carts, orders, and revenue per day |
Pagination
All responses include totalRecords, hasMore, page, and limit. Increment page while hasMore is true to fetch subsequent pages.
Each response also includes a queryExecutionId. Pass it back in a follow-up request to re-fetch the same result without re-running the query — results are cached for 24 hours.
{
"queryExecutionId": "a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"page": 1,
"limit": 25
}
page and limit must match the original request — the cached result contains only the rows for that page.
Dry Run
Add "dryRun": true to preview the generated SQL without executing it. The response will contain a sql field and no data.
{
"dimensions": ["keyword"],
"measures": [{ "field": "item_total", "fn": "sum", "alias": "revenue" }],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["sale"] },
{ "member": "event_year", "operator": "equals", "values": ["2026"] }
],
"dryRun": true
}
Performance Tips
- Always include
event_yearand/orevent_monthin filters when targeting a specific period. These are partition keys and dramatically reduce data scanned. - Use date shortcuts (
last_30_days,last_month, etc.) for relative windows — they resolve server-side and keep your query always current. - Prefer pre-built report endpoints for common metrics — they query pre-aggregated tables and are faster than raw
summaryqueries. - Set
limitto the smallest value you need. UsehasMoreandtotalRecordsto decide whether to page further. - Re-use
queryExecutionIdfor retries or paginated UIs — avoids re-running expensive queries.
Response Format
All endpoints return the same JSON structure regardless of query type.
{
"columns": [
{ "name": "keyword", "type": "string" },
{ "name": "revenue", "type": "number" }
],
"data": [
{ "keyword": "running shoes", "revenue": "4521.50" },
{ "keyword": "blue jeans", "revenue": "3890.00" }
],
"totalRecords": 842,
"page": 1,
"limit": 25,
"hasMore": true,
"queryExecutionId": "a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
| Field | Description |
|---|---|
columns | Column names and inferred types (string, number, date) |
data | Array of rows. Each row is a flat key-value map; all values are strings. Null fields are omitted. |
totalRecords | Total matching rows across all pages |
hasMore | true if additional pages are available |
queryExecutionId | Pass back to re-fetch this result without re-running the query |
sql | Generated SQL. Only present when dryRun: true |
Errors
| Status | Meaning |
|---|---|
400 | Invalid request — unknown field, unsupported operator, or missing dimensions/measures |
401 | API key missing or invalid |
404 | Account not configured for analytics |
429 | Rate limit exceeded |
500 | Query execution failed |
502 | Natural language translation failed — rephrase or use structured mode |
{
"error": "Bad Request",
"message": "Unknown dimension: 'sale_item_id'."
}
Sample Queries
Pre-built Reports
Daily search volume
{
"startDate": "2026-06-01",
"endDate": "2026-06-30"
}
Top search keywords
{
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"limit": 25
}
Keywords returning few results
{
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"limit": 50
}
Spelling correction activity
{
"startDate": "2026-06-01",
"endDate": "2026-06-30"
}
Daily sales funnel
{
"startDate": "2026-06-01",
"endDate": "2026-06-30"
}
Revenue by product and keyword
{
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"limit": 100
}
Recommendation widget performance
{
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"limit": 50
}
Natural Language
Pass a plain English question using the query field. The API interprets it and runs the appropriate query automatically.
Search volume
{ "query": "How many searches did we have last month?" }
Top keywords by revenue
{ "query": "Top 10 keywords by revenue in June 2026" }
Poor-performing search terms
{ "query": "Which keywords returned no results last 30 days?" }
Recommendation performance
{ "query": "Which recommendation widgets drove the most revenue this month?" }
Sales trend
{ "query": "Show me monthly revenue for 2026" }
Ad-hoc Queries
Click-through rate by keyword
{
"dimensions": ["keyword"],
"measures": [
{ "field": "tracking_id", "fn": "count_distinct", "alias": "searches" },
{ "field": "click_item_id", "fn": "count_distinct", "alias": "clicks" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["search"] },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_30_days"] }
],
"order": [{ "field": "searches", "dir": "desc" }],
"limit": 50
}
Cart abandonment — added to cart but did not purchase
{
"dimensions": ["keyword"],
"measures": [{ "field": "visit_id", "fn": "count_distinct", "alias": "abandoned_visits" }],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["search"] },
{ "member": "had_cart", "operator": "equals", "values": ["true"] },
{ "member": "had_sale", "operator": "equals", "values": ["false"] },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_month"] }
],
"order": [{ "field": "abandoned_visits", "dir": "desc" }]
}
Widget revenue (recommendation-attributed)
{
"dimensions": ["widget_id", "widget_name"],
"measures": [
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" },
{ "field": "item_quantity", "fn": "sum", "alias": "units_sold" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["rec_click"] },
{ "member": "item_total", "operator": "set" },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_30_days"] }
],
"order": [{ "field": "revenue", "dir": "desc" }]
}
Top recommended items by widget
{
"dimensions": ["widget_id", "widget_name", "item_id", "item_name"],
"measures": [
{ "field": "*", "fn": "count", "alias": "clicks" },
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["rec_click"] },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_30_days"] }
],
"order": [{ "field": "clicks", "dir": "desc" }],
"limit": 50
}
Monthly revenue trend
{
"dimensions": ["event_year", "event_month"],
"measures": [
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" },
{ "field": "item_quantity", "fn": "sum", "alias": "units_sold" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["sale"] },
{ "member": "event_year", "operator": "equals", "values": ["2026"] }
],
"order": [{ "field": "event_month", "dir": "asc" }]
}
High-revenue keywords — using having
{
"dimensions": ["keyword"],
"measures": [
{ "field": "item_total", "fn": "sum", "alias": "revenue" },
{ "field": "order_number", "fn": "count_distinct", "alias": "orders" }
],
"filters": [
{ "member": "event_type", "operator": "equals", "values": ["sale"] },
{ "member": "event_date", "operator": "inDateRange", "values": ["last_month"] }
],
"having": [
{ "member": "revenue", "operator": "gte", "values": ["1000"] }
],
"order": [{ "field": "revenue", "dir": "desc" }]
}