Hoverboard API (v1)
The Hoverboard API lets Enterprise customers pull reporting data into their own dashboards, BI tools, and client reports. It is read-only and every request is scoped to the organization that owns the API key.
- Base URL:
https://auth.gethoverboard.com/functions/v1/api-gateway - Format: JSON (
Content-Type: application/json) - Methods:
GETonly - Versioning: paths are not prefixed with
/v1. (Older integrations may still send/v1/...; it is accepted for backward compatibility but is no longer required.)
Authentication
Send your key as a bearer token on every request:
Authorization: Bearer hb_live_xxxxxxxxxxxxxxxxxxxxxxxx
Keys are created in the dashboard under Settings → API Keys (visible to org admins on Enterprise plans). The full key is displayed only once at creation — store it in your secret manager. Keys are stored hashed; they can be revoked at any time, which takes effect immediately.
Requirements:
- The organization's plan must have API access enabled (Enterprise), otherwise requests return
403. - Keys are organization-scoped: you can only read data belonging to that organization (and, for the organizations endpoint, its child organizations).
Never expose an API key in browser or mobile client code. Call the API from your server.
Rate limits
| Limit | Value |
|---|---|
| Requests per organization per day (UTC) | 10,000 |
Max limit per page | 1,000 |
Default limit per page | 100 |
Exceeding the daily limit returns 429 Too Many Requests. The counter resets at 00:00 UTC.
Every request is logged (method, path, query params, status code, response time) for usage auditing.
Common query parameters
Pagination (list endpoints):
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 100 | Clamped to 1–1000 |
offset | integer | 0 | Items to skip |
Date range (results, sources, urls, stats, visibility, competitors, prompt stats, sentiment):
| Parameter | Type | Default | Notes |
|---|---|---|---|
start_date | ISO date/datetime | end_date − 30 days | Inclusive |
end_date | ISO date/datetime | now | Inclusive |
Dates accept YYYY-MM-DD or full ISO-8601 timestamps. Responses echo the resolved range in date_range.
Bare dates resolve to UTC: start_date=2026-08-12 begins at 00:00:00.000Z; end_date=2026-08-12 includes that day through 23:59:59.999Z. Explicit timestamps retain their supplied instant on timestamp-based endpoints; daily rollup endpoints (sources, urls, visibility, and daily brand trends) use the resolved UTC calendar dates.
Canonical values
AI sources. Every source value returned by the API — enabled_sources, response_metadata.source, sources[].source_type, and the source field on aggregated rows — uses these canonical values:
| Value | AI surface |
|---|---|
chatgpt | ChatGPT (OpenAI) |
perplexity | Perplexity |
gemini | Google Gemini |
google_ai_overview | Google AI Overviews |
google_ai_mode | Google AI Mode |
claude | Claude |
Internal provider labels (for example openai or google_ai_overviews) are never returned. Where a request accepts a source value, both the canonical value and the legacy internal label are accepted.
Locations. location_name is normalized to a de-duplicated City, State, Country string (e.g. El Paso, Texas, United States). Repeated geography segments and redundant state abbreviations are removed.
Endpoints
1. GET /organizations
Returns the organization tied to the API key plus any child organizations.
{
"organizations": [
{
"id": "145a5bee-...",
"name": "Example Organization",
"description": null,
"website": "https://example.com",
"owner_id": "9f0c...",
"parent_organization_id": null,
"created_at": "2026-01-14T18:02:11.000Z"
}
]
}
2. GET /projects
Lists projects in the organization, newest first. Supports pagination.
{
"data": [
{
"id": "0d5f...",
"name": "Example Client",
"business_name": "Example Client",
"alternative_names": ["Example Client Legal Group"],
"website_url": "https://example-client.com",
"schedule": "daily",
"custom_interval": null,
"enabled_sources": ["chatgpt", "perplexity", "gemini", "google_ai_overview"],
"location_code": 2840,
"location_name": "United States",
"created_at": "2026-02-01T12:00:00.000Z",
"updated_at": "2026-08-01T09:31:22.000Z",
"organization_id": "145a5bee-..."
}
],
"pagination": { "limit": 100, "offset": 0, "total": 12 }
}
3. GET /projects/:id
Single project lookup. Returns 404 if the project is not in your organization.
{
"project": {
"id": "0d5f...",
"business_name": "Example Client",
"alternative_names": ["..."],
"organization_id": "145a5bee-..."
}
}
4. GET /projects/:id/prompts
Active (non-archived) prompts for a project. Supports pagination. intent_type is the classified search intent (null until classified); branded is true when the prompt names your business or an alternative name.
intent_type is one of: best_in_category, comparison, buying_criteria, use_case, problem_aware, informational. intent_confidence is the classifier's confidence (0–1). A prompt is branded when it contains your business name or one of its alternative names as whole words — this is computed from your project settings, not by the classifier.
{
"data": [
{
"id": "b91e...",
"project_id": "0d5f...",
"text": "best example service in example city",
"archived": false,
"exclude_from_visibility": false,
"tags": ["core", "local"],
"intent_type": "best_in_category",
"intent_confidence": 0.94,
"branded": false,
"created_at": "2026-02-01T12:04:00.000Z",
"updated_at": "2026-02-01T12:04:00.000Z"
}
],
"pagination": { "limit": 100, "offset": 0, "total": 25 }
}
5. GET /projects/:id/results
Individual AI answer checks (one row per prompt × model × run). Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "2026-07-13T00:00:00.000Z", "end": "2026-08-12T00:00:00.000Z" },
"data": [
{
"id": "77aa...",
"check_id": "3c9d...",
"project_id": "0d5f...",
"prompt_id": "b91e...",
"prompt_text_snapshot": "best example service in example city",
"response_text": "Here are several highly rated...",
"timestamp": "2026-08-11T14:22:03.000Z",
"brand_mentioned": true,
"brand_position": 2,
"other_brands": ["Competitor A", "Competitor B"],
"recommended": true,
"recommended_probability": 0.91,
"citations": [
{ "url": "https://example-directory.com/...", "title": "Top Example Services" }
],
"sources": [
{ "url": "https://example-directory.com/...", "domain": "example-directory.com", "title": "Top Example Services", "site_name": "Example Directory", "position": 1, "is_inline_cited": true, "source_type": "chatgpt" }
],
"query_fanouts": ["best example service example city"],
"response_metadata": { "source": "chatgpt" },
"sentiment_score": 0.72,
"sentiment_label": "positive",
"sentiment_confidence": 0.88,
"sentiment_context": "Described as highly experienced..."
}
],
"pagination": { "limit": 100, "offset": 0, "total": 3120 }
}
Field notes:
brand_position— 1-based rank of your brand in the answer;nullif not mentioned.other_brands— competitor brands detected in the same answer.recommended— whether the answer recommends your brand (nullwhen not mentioned or not yet scored).sources— every saved source for the answer;citationsis the inline-cited subset.- Perplexity numbered markers such as
[1]and[8]identify cited provider source positions. AI Mode citation pills, answer links and explicit passage-to-source associations establish citations; retrieved sources alone do not. prompt_text_snapshot— saved prompt at check time; if absent, falls back to the current text of the same project prompt. Remainsnullif neither is available. This fallback does not modify stored snapshots.query_fanouts— searches the AI ran while answering (ChatGPT and others when available).check_id— the check run this answer came from; answers from the same run share it.- Optional
sourcequery parameter filters to one AI surface (e.g.?source=chatgpt).modelis accepted as an alias. - Answers from every AI surface your project tracks are included, so row counts are higher than in earlier API versions.
response_metadata.source— which AI surface produced the answer (chatgpt,perplexity,gemini,google_ai_overview,google_ai_mode).
6. GET /projects/:id/sources
Aggregated domain-level citation data for the date range. Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "2026-07-13T00:00:00.000Z", "end": "2026-08-12T00:00:00.000Z" },
"data": [
{
"domain": "example-directory.com",
"citation_count": 184,
"prompt_count": 41,
"brand_mentioned_count": 22
}
],
"pagination": { "limit": 100, "offset": 0 }
}
Counts are distinct-aware: prompt_count is the number of unique prompts that cited the domain.
7. GET /projects/:id/urls
Same as sources but aggregated at the individual URL level. Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"data": [
{
"url": "https://www.example-list.com/example-services/tx/example-city/",
"domain": "example-list.com",
"citation_count": 37,
"prompt_count": 14
}
],
"pagination": { "limit": 100, "offset": 0 }
}
8. GET /projects/:id/stats
Daily visibility trend plus brand trend series for the date range. No pagination.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"daily_visibility": [
{ "date": "2026-08-11", "total_checks": 96, "mentions": 41, "visibility_pct": 42.7 }
],
"brand_trends": [
{ "date": "2026-08-11", "brand_name": "Example Client", "mention_count": 41 }
]
}
Brand names are normalized server-side, so variants of your brand (e.g. "Example Client" and "Example Client Legal Group") are merged into one series.
8b. GET /projects/:id/visibility
Daily brand visibility broken down by model/source — the pre-aggregated series behind the "Model Visibility" chart. No pagination, one request covers the whole date range.
Query parameters: start_date, end_date, and optional source (comma-separated list to filter, e.g. source=chatgpt,perplexity). Both canonical values (chatgpt, google_ai_overview, etc.) and legacy internal labels (openai, google_ai_overviews) are accepted.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"overall": { "total_checks": 2880, "mentioned_checks": 601, "visibility_pct": 20.9 },
"by_source": [
{ "source": "google_ai_overview", "total_checks": 620, "mentioned_checks": 197, "visibility_pct": 31.8 },
{ "source": "google_ai_mode", "total_checks": 601, "mentioned_checks": 170, "visibility_pct": 28.3 },
{ "source": "chatgpt", "total_checks": 615, "mentioned_checks": 122, "visibility_pct": 19.8 }
],
"daily": [
{ "date": "2026-08-11", "source": "chatgpt", "total_checks": 21, "mentioned_checks": 4, "visibility_pct": 19.0 }
]
}
by_source gives the period totals (the horizontal bars); daily gives one row per date × source for time-series charts. Percentages are computed from the summed counts, not averaged across days.
9. GET /projects/:id/competitors
Competitor leaderboard for the date range. No pagination.
Brand-name variants are collapsed server-side (e.g. "Greene Broillet", "Greene Broillet & Wheeler" and "Greene Broillet & Wheeler, LLP" return as one canonical brand), matching the dashboard exactly. The same collapsing applies to brand_trends in /stats.
Each brand is returned as multiple bucketed rows: one for the current window, one for the previous comparison window, and one per AI source. To get the overall leaderboard, filter to bucket === "current". Do not sum across buckets or you will double- or triple-count mentions.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"competitors": [
{
"brand_name": "Example Client",
"is_target": true,
"bucket": "current",
"mention_count": 412,
"visibility_pct": 42.7,
"avg_position": 2.1
},
{
"brand_name": "Example Client",
"is_target": true,
"bucket": "previous",
"mention_count": 380,
"visibility_pct": 41.2,
"avg_position": 2.2
},
{
"brand_name": "Example Client",
"is_target": true,
"bucket": "src:chatgpt",
"mention_count": 122,
"visibility_pct": 19.8,
"avg_position": 2.0
},
{
"brand_name": "Competitor A",
"is_target": false,
"bucket": "current",
"mention_count": 388,
"visibility_pct": 40.2,
"avg_position": 2.6
}
]
}
Your own brand appears once per bucket with is_target: true; all detected variants are merged into that row. To reproduce the dashboard "Top Brands" table, use only rows where bucket is current.
Note on mention counts: the mention_count for your target brand in the current bucket should match the sum of mentions from /projects/:id/stats for the same date range. If you need a single "total mentions" number, use /stats; /competitors is meant for breakdowns and comparisons.
10. GET /projects/:id/prompts/stats
Per-prompt performance for the date range — the data behind the Tracked Prompts report. No pagination.
Query parameters: start_date, end_date, and optional source (defaults to all; accepts the canonical source values below). model is accepted as an alias for source.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"source": "all",
"data": [
{
"prompt_id": "b91e...",
"prompt_text": "best example service in example city",
"tags": ["core", "local"],
"excluded_from_visibility": false,
"total_checks": 120,
"mentions": 47,
"excluded_checks": 0,
"visibility_pct": 39,
"trend": [{ "date": "2026-08-01", "mention_percent": 42 }],
"top_competitors": [{ "brand_name": "Competitor A", "mentions": 61 }],
"query_fanouts": ["example service near me"],
"sources": ["chatgpt", "perplexity", "gemini"]
}
]
}
11. GET /projects/:id/sentiment
Brand sentiment for the date range — overall, versus the previous equal-length period, per AI source, per day, per category and by theme. Only answers where your brand was mentioned and scored are included. No pagination.
This endpoint is served by the same shared sentiment summary that powers the dashboard, project sentiment page, web reports and PDF reports, so the numbers always match the product.
Query params: start_date, end_date, and optional source (chatgpt, perplexity, gemini, google_ai_overview, google_ai_mode, or all). model is accepted as an alias for source.
Scores range from -1 to 1. Mentions fall into four buckets: positive, negative, neutral and mixed (both clearly positive and clearly negative signals in the same answer). average_score excludes mixed and unclear mentions.
{
"project": { "id": "0d5f...", "business_name": "Example Client" },
"date_range": { "start": "...", "end": "..." },
"source": "all",
"summary": {
"total": 261,
"positive": 234,
"neutral": 25,
"negative": 1,
"mixed": 1,
"unclear": 0,
"average_score": 0.6474,
"positive_pct": 89.7,
"negative_pct": 0.4,
"neutral_pct": 9.6,
"mixed_pct": 0.4
},
"previous_period": {
"total": 240,
"positive": 210,
"neutral": 28,
"negative": 2,
"mixed": 0,
"average_score": 0.6301
},
"change": {
"total": 21,
"positive": 24,
"negative": -1,
"average_score": 0.0173,
"positive_pct": 2.2,
"negative_pct": -0.4
},
"by_source": [
{
"source": "chatgpt",
"total": 81,
"positive": 74,
"neutral": 7,
"negative": 0,
"mixed": 0,
"average_score": 0.6785,
"positive_pct": 91.4
}
],
"daily": [
{
"date": "2026-08-07",
"total": 45,
"positive": 39,
"neutral": 6,
"negative": 0,
"mixed": 0,
"average_score": 0.6369
}
],
"by_category": [
{ "category": "reputation", "total": 62, "positive": 58, "neutral": 4, "negative": 0, "mixed": 0, "average_score": 0.71 }
],
"top_themes": [
{ "theme": "Strong client reviews", "total": 34, "positive": 33, "neutral": 1, "negative": 0, "mixed": 0, "average_score": 0.74 }
]
}
Errors
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — missing, invalid, expired, or revoked API key |
| 403 | Forbidden — API access not enabled for this organization's plan |
| 404 | Not found — unknown endpoint, or resource not in your organization |
| 429 | Too many requests — daily rate limit exceeded |
| 504 | Gateway timeout — query took too long; narrow the date range or retry |
| 500 | Internal server error |
All errors share the same shape:
{ "error": "Project not found or not accessible" }
Examples
cURL
curl -H "Authorization: Bearer $HOVERBOARD_API_KEY" \
"https://auth.gethoverboard.com/functions/v1/api-gateway/projects?limit=50"
Date-filtered results
curl -H "Authorization: Bearer $HOVERBOARD_API_KEY" \
"https://auth.gethoverboard.com/functions/v1/api-gateway/projects/PROJECT_ID/results?start_date=2026-07-01&end_date=2026-07-31&limit=500"
Node (fetch)
const BASE = "https://auth.gethoverboard.com/functions/v1/api-gateway";
async function hb(path, params = {}) {
const url = new URL(BASE + path);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.HOVERBOARD_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error);
return res.json();
}
const { data: projects } = await hb("/projects");
const stats = await hb(`/projects/${projects[0].id}/stats`, {
start_date: "2026-08-01",
});
Python
import os, requests
BASE = "https://auth.gethoverboard.com/functions/v1/api-gateway"
HEADERS = {"Authorization": f"Bearer {os.environ['HOVERBOARD_API_KEY']}"}
def hb(path, **params):
r = requests.get(BASE + path, headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
return r.json()
projects = hb("/projects")["data"]
sources = hb(f"/projects/{projects[0]['id']}/sources", limit=200)
Paginating everything
async function all(path, params = {}) {
const out = [];
let offset = 0;
const limit = 1000;
for (;;) {
const page = await hb(path, { ...params, limit, offset });
out.push(...page.data);
if (page.data.length < limit) break;
offset += limit;
}
return out;
}
Best practices
- Cache aggressively. Reporting data updates once per scheduled check run; hourly caching is usually plenty and keeps you well under the daily limit.
- Use date ranges. Pulling
resultswithout a range defaults to the last 30 days; narrow it further for faster responses. - Prefer aggregates.
stats,visibility,sources,urls, andcompetitorsare pre-aggregated server-side and are far cheaper than paginating rawresults. - Rotate keys periodically and issue separate keys per integration so one can be revoked without affecting the others.
- Handle 429 and 504. Back off exponentially on
429; the daily counter resets at 00:00 UTC. On504, retry with a narrower date range or simply retry the same request — rollups and indexes usually keep these rare.