Hoverboard AI API



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: GET only
  • 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; null if 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 results without a range defaults to the last 30 days; narrow it further for faster responses.
  • Prefer aggregates. stats, sources, urls, and competitors are pre-aggregated server-side and are far cheaper than paginating raw results.
  • 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.

Create your account