Hoverboard API (v1) Documentation
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://nrxgvdqnjcpgaglljnrn.supabase.co/functions/v1/api-gateway - Format: JSON (
Content-Type: application/json) - Methods:
GETonly - Versioning: all paths are prefixed with
/v1
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, competitors):
| 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.
Endpoints
1. GET /v1/organizations
Returns the organization tied to the API key plus any child organizations.
{
"organizations": [
{
"id": "145a5bee-...",
"name": "Team First",
"description": null,
"website": "https://teamfirst.com",
"owner_id": "9f0c...",
"parent_organization_id": null,
"created_at": "2026-01-14T18:02:11.000Z"
}
]
}
2. GET /v1/projects
Lists projects in the organization, newest first. Supports pagination.
{
"data": [
{
"id": "0d5f...",
"name": "Hill Law Firm",
"business_name": "Hill Law Firm",
"alternative_names": ["Hill Law Firm Accident and Injury Lawyers"],
"website_url": "https://hilllawfirm.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 /v1/projects/:id
Single project lookup. Returns 404 if the project is not in your organization.
{
"project": {
"id": "0d5f...",
"business_name": "Hill Law Firm",
"alternative_names": ["..."],
"organization_id": "145a5bee-..."
}
}
4. GET /v1/projects/:id/prompts
Active (non-archived) prompts for a project. Supports pagination.
{
"data": [
{
"id": "b91e...",
"project_id": "0d5f...",
"text": "best personal injury lawyer in san antonio",
"archived": false,
"exclude_from_visibility": false,
"tags": ["core", "local"],
"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 /v1/projects/:id/results
Individual AI answer checks (one row per prompt × model × run). Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Hill Law Firm" },
"date_range": { "start": "2026-07-13T00:00:00.000Z", "end": "2026-08-12T00:00:00.000Z" },
"data": [
{
"id": "77aa...",
"project_id": "0d5f...",
"prompt_id": "b91e...",
"prompt_text_snapshot": "best personal injury lawyer in san antonio",
"response_text": "Here are several highly rated...",
"timestamp": "2026-08-11T14:22:03.000Z",
"brand_mentioned": true,
"brand_position": 2,
"other_brands": ["Thomas J. Henry", "Ketterman Rowland"],
"citations": [
{ "url": "https://ontoplist.com/...", "title": "Top Car Accident Lawyers" }
],
"response_metadata": { "source": "chatgpt", "model": "gpt-5" },
"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.response_metadata.source— which AI surface produced the answer (chatgpt,perplexity,gemini,google_ai_overview,google_ai_mode).
6. GET /v1/projects/:id/sources
Aggregated domain-level citation data for the date range. Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Hill Law Firm" },
"date_range": { "start": "2026-07-13T00:00:00.000Z", "end": "2026-08-12T00:00:00.000Z" },
"data": [
{
"domain": "avvo.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 /v1/projects/:id/urls
Same as sources but aggregated at the individual URL level. Supports pagination and date range.
{
"project": { "id": "0d5f...", "business_name": "Hill Law Firm" },
"date_range": { "start": "...", "end": "..." },
"data": [
{
"url": "https://www.ontoplist.com/car-accident-lawyers/tx/san-antonio/",
"domain": "ontoplist.com",
"citation_count": 37,
"prompt_count": 14
}
],
"pagination": { "limit": 100, "offset": 0 }
}
8. GET /v1/projects/:id/stats
Daily visibility trend plus brand trend series for the date range. No pagination.
{
"project": { "id": "0d5f...", "business_name": "Hill Law Firm" },
"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": "Hill Law Firm", "mention_count": 41 }
]
}
Brand names are normalized server-side, so variants of your brand (e.g. "Hill Law Firm" and "Hill Law Firm Accident & Injury Lawyers") are merged into one series.
9. GET /v1/projects/:id/competitors
Competitor leaderboard for the date range. No pagination.
{
"project": { "id": "0d5f...", "business_name": "Hill Law Firm" },
"date_range": { "start": "...", "end": "..." },
"competitors": [
{
"brand_name": "Hill Law Firm",
"is_target": true,
"mention_count": 412,
"visibility_pct": 42.7,
"avg_position": 2.1
},
{
"brand_name": "Thomas J. Henry",
"is_target": false,
"mention_count": 388,
"visibility_pct": 40.2,
"avg_position": 2.6
}
]
}
Your own brand appears once with is_target: true; all detected variants are merged into that row.
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 |
| 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://nrxgvdqnjcpgaglljnrn.supabase.co/functions/v1/api-gateway/v1/projects?limit=50"
Date-filtered results
curl -H "Authorization: Bearer $HOVERBOARD_API_KEY" \
"https://nrxgvdqnjcpgaglljnrn.supabase.co/functions/v1/api-gateway/v1/projects/PROJECT_ID/results?start_date=2026-07-01&end_date=2026-07-31&limit=500"
Node (fetch)
const BASE = "https://nrxgvdqnjcpgaglljnrn.supabase.co/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("/v1/projects");
const stats = await hb(`/v1/projects/${projects[0].id}/stats`, {
start_date: "2026-08-01",
});
Python
import os, requests
BASE = "https://nrxgvdqnjcpgaglljnrn.supabase.co/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("/v1/projects")["data"]
sources = hb(f"/v1/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,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 with exponential backoff; the limit resets at 00:00 UTC.

