Workspace API
Your brand's AI-search visibility, as data. The Workspace API gives you programmatic, read-only access to everything your workspace tracks: daily visibility scores across 8 AI engines, the prompt-level answers behind them, every cited source, competitor share of voice, and, when you connect Google Analytics and Search Console, the visits and search demand on the other end of those answers. That data can live where your team already works, whether that is your data warehouse, your BI dashboards, or the tools you build in-house.
Availability. The Workspace API is available on enterprise plans. If you'd like access, or want to talk through an integration before committing, email hello@honeyb.ai. We typically have keys issued and a first sync running within days.
Why build on the Honeyb API
- Your warehouse, your joins. Sync visibility and citation data into Snowflake, BigQuery, or any store, and join it with your CRM, traffic, and content data to answer questions no single dashboard can.
- The whole funnel, not just the top. Mentioned, cited, clicked: the traffic endpoints carry the AI-attributed sessions and the search queries behind your pages, so one query can put an answer's citation next to the visits it produced.
- Numbers that match the product. The API reads the same data with the same counting rules as the Honeyb dashboard. What your team sees in the app is what your pipeline receives.
- A contract you can depend on.
/api/v1is additive-only and engine identifiers are stable slugs; the Versioning section below is the full promise. - Your own data, always. One API key reads exactly one workspace. Keys are stored only as SHA-256 hashes, revocable instantly, and structurally unable to see another customer's data: the scoping is the credential, not a filter.
Quick start
Every request carries your API key as a bearer token:
curl https://app.honeyb.ai/api/v1/visibility \
-H "Authorization: Bearer <YOUR_API_KEY>"
{
"workspaceId": "8b1e...",
"latestVisibilityScore": 42.5,
"latestTotalMentions": 34,
"latestTotalPrompts": 80,
"latestAnalysisAt": "2026-08-03T06:12:04.000Z",
"peakVisibilityScore": 51.0,
"peakVisibilityAt": "2026-07-20T06:10:11.000Z",
"dailyScores": [
{ "date": "2026-08-02", "brandMentions": 31, "totalQueries": 80, "visibilityScore": 38.75 }
]
}
All responses are JSON over HTTPS; all timestamps are ISO 8601 UTC. The API is server-to-server by design, with no CORS, so your key never ships to a browser.
Managing your keys
API access is enabled for your workspace as part of your contract. Once it is on, any Owner or Editor of your workspace manages keys directly in the app under Settings → API access, with no support ticket needed:
- Generate. The key is shown exactly once, so store it in a secrets manager. We keep only a hash, so if a key is lost, revoke it and generate a new one.
- Rotate. Generate the new key first, then revoke the old one, so your integration never has a gap. Revocation takes effect immediately.
- If a key leaks, revoke it in the app and it stops working on the next request. Only key hashes are stored on our side, so a database leak yields no usable credentials.
Versioning
/api/v1 is additive-only: fields may be added to responses, but never
removed or retyped. Breaking changes ship as /api/v2 with a documented
deprecation window. Build your parsers to ignore unknown fields.
Errors and limits
Errors are JSON with a stable machine-readable code:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_query | A query parameter is malformed |
| 401 | unauthorized | Missing, invalid, revoked, or expired key, or API access is currently disabled for the workspace (your keys are kept and resume working when it is re-enabled) |
| 429 | rate_limited | Over the request budget; back off and retry |
| 500 | internal_error | Our side; monitored and alerted on |
Standard limit is 1,000 requests per key per hour, comfortably above a daily warehouse sync, and can be raised per contract. Failed authentication attempts are separately throttled per IP.
Pagination
Two schemes, matched to the data:
- Offset (
page+pageSize, max 200 per page) on/prompts,/resultsand/search-queries. Offset pagination stops at 2,000 rows per filter window; on/resultsand/search-queries, narrow thesinceorwindowDayswindow instead of paging further (/promptshas no window and typical prompt sets sit well inside the cap). - Keyset (
cursor) on/citations, the recommended way to sync incrementally: pass the previous response'snextCursoruntil it returnsnull, and persist your lastsincecheckpoint between syncs.
Endpoints
| Endpoint | What it returns |
|---|---|
GET /api/v1/visibility | Visibility summary + up to 365 days of daily history |
GET /api/v1/prompts | The tracked prompt set (topic, intent, search volume) |
GET /api/v1/results | Prompt-level AI answers, one row per prompt × engine × run |
GET /api/v1/citations | Every URL cited in your answers, built for incremental sync |
GET /api/v1/competitors | Share of voice per brand observed in your answers |
GET /api/v1/web-traffic | Sessions and conversions with the AI-attributed slice (Google Analytics) |
GET /api/v1/search-queries | Search queries earning impressions and clicks (Search Console) |
GET /api/v1/visibility
The workspace's visibility summary: latest score, peak, and up to 365 days of daily history (ascending by date). The quick-start example above shows the full response shape.
Query parameters: none.
visibilityScore is the percentage of tracked prompt runs whose answers
mentioned your brand that day. New workspaces return null metrics and an
empty dailyScores until the first analysis completes.
GET /api/v1/prompts
The tracked prompt set (active and paused). Internal draft suggestions are never returned.
Query parameters: page (default 1), pageSize (default 50, max 200).
{
"data": [
{
"id": "3f9a...",
"prompt": "best enterprise billing software",
"status": "active",
"topic": "Enterprise Billing",
"intent": "commercial",
"searchVolume": 720,
"tags": [],
"createdAt": "2026-07-30T09:00:00.000Z"
}
],
"page": 1,
"pageSize": 50,
"total": 100
}
GET /api/v1/results
Prompt-level AI answers: one row per prompt × engine × daily run, newest first, carrying the full answer text, mention counts, competitors named alongside you, and sentiment.
Query parameters: page, pageSize, since (ISO 8601; default:
last 90 days).
{
"data": [
{
"id": "b2c4...",
"promptId": "3f9a...",
"prompt": "best enterprise billing software",
"engine": "chatgpt",
"answer": "For enterprise revenue management, the leading options are...",
"brandMentions": 1,
"totalMentions": 5,
"competitorsMentioned": ["Zuora", "Chargebee"],
"sentiment": "positive",
"sentimentConfidence": 82,
"createdAt": "2026-08-03T06:11:52.000Z"
}
],
"page": 1,
"pageSize": 50,
"total": 812
}
Notes:
answeris the engine's own text. It is uncontrolled third-party content that may name individuals or mirror live web content, and is provided for your internal analysis.- Rows where the provider call failed are excluded.
engineis one of the stable slugschatgpt,gemini,claude,perplexity,grok,deepseek,google_ai_mode,google_ai_overviews(the set matches your plan's coverage). Slugs never change when we upgrade the underlying models. Rows from before April 2026 can carry legacy model identifiers outside this set (e.g.gpt-4o-mini); treat unrecognized values as additional engines rather than rejecting the row.- Rows appear as each engine's daily run completes, so
totalfor the current day grows until the cycle finishes (~07:00 UTC). Sync after that and counts are stable.
GET /api/v1/citations
Every URL cited in your answers; the best endpoint for incremental sync. Cursor-paginated in stable ascending order.
Query parameters: pageSize (default 50, max 200), since (ISO
8601; default: last 90 days), cursor (opaque, from the previous
nextCursor).
{
"data": [
{
"id": "d81f...",
"url": "https://example.com/best-billing-software",
"domain": "example.com",
"pageType": "listicle",
"position": 2,
"brandMentioned": false,
"sentiment": null,
"createdAt": "2026-08-03T06:11:52.000Z"
}
],
"pageSize": 50,
"nextCursor": "MTc1NDIw..."
}
createdAt is inherited from the analysis run (not row insert time), so a
since checkpoint never misses late-arriving rows. Iterate: request →
process data → pass nextCursor back → stop when nextCursor is
null.
The default 90-day window applies to the first page only: once you're
paging with a cursor, the cursor bounds the scan, so a slow sync can
never skip rows at the window edge. An explicit since is always
honoured.
Within one cursor walk, every row is delivered exactly once. Across syncs,
checkpoint the createdAt of the last row you processed and pass it as
the next sync's since: the boundary is inclusive, so rows sharing that
exact timestamp are re-delivered rather than skipped. De-duplicate on
id and a nightly sync can neither skip nor double-count a citation.
GET /api/v1/competitors
Share of voice across every brand observed in your own tracked answers, computed with the same counting rules as your dashboard.
Query parameters: windowDays (default 30, max 365).
{
"workspaceId": "8b1e...",
"windowDays": 30,
"scannedResults": 1840,
"isTruncated": false,
"totalBrandMentions": 412,
"competitors": [
{ "brand": "Zuora", "mentions": 118, "shareOfVoicePercent": 28.64, "isWorkspaceBrand": false },
{
"brand": "BillingPlatform",
"mentions": 96,
"shareOfVoicePercent": 23.3,
"isWorkspaceBrand": true
}
]
}
Up to the 50 most-mentioned brands are returned, and your own brand is always among them (with 0 mentions if the engines never mentioned it). Only brands appearing in your workspace's own answers are reported; this endpoint never exposes other customers' data.
The calculation covers the most recent 2,000 answers in the window
(scannedResults says how many were used). If the window matched more
than that, isTruncated is true and the shares reflect the most
recent slice. Narrow windowDays until isTruncated is false for
full-window coverage.
GET /api/v1/web-traffic
Sessions and conversions from your Google Analytics 4 property over the
window, with the slice attributed to AI assistants and the landing pages and
referrers behind it. This is the "clicked" half of the funnel: pair it with
/citations to see which cited pages actually receive AI-driven visits.
Requires a Google Analytics property connected in the app under Web
traffic. Until one is connected, the endpoint answers with
totals: null.
Query parameters: windowDays (default 30, max 90).
{
"workspaceId": "8b1e...",
"windowDays": 30,
"analytics": { "connected": true, "latestDataDate": "2026-08-24" },
"totals": {
"sessions": 48210,
"conversions": 612,
"aiSessions": 1840,
"aiConversions": 47,
"aiSharePercent": 3.8
},
"topAiLandingPages": [
{ "landingPage": "/pricing", "sessions": 412, "conversions": 19 }
],
"topAiSources": [{ "source": "chatgpt.com", "sessions": 980 }]
}
Notes:
totalsisnull, never zeroes, whenever nothing was measured in the window, andanalyticstells you which case you are in:connected: falsemeans no property is connected;connected: truewith anulllatestDataDatemeans the first sync has not landed; alatestDataDateolder than the window means the sync has stopped running (a revoked Google authorisation is the usual cause) and the window went unmeasured. An empty response is "not measured", never "traffic fell to zero".aiSessionsis a floor, not a census. Attribution reads the referrer and several assistants strip it, so real AI-driven traffic is at least this number. Report it as "at least", the way the dashboard does.latestDataDateis your staleness check. Traffic is re-synced nightly, so a date more than a day or two behind means the sync has not caught up and the window is partial. If it falls behind the whole window,totalsgoes back tonullrather than reporting the zero that partial data would imply. Reconnect the property in the app if it stays stale.windowDaysis capped at 90 because 90 days is the window we keep synced. A larger value comes back clamped, with the clamped number echoed inwindowDays.
GET /api/v1/search-queries
Every Search Console query that earned impressions for your site in the
window, grouped across days and pages, ordered by impressions descending.
This is the demand signal behind AI answers: isPromptLike marks
question-shaped queries that map to prompts, isLongTail marks phrasing of
four words or more.
Requires a Search Console property connected in the app under Web traffic.
Query parameters: windowDays (default 30, max 90), page
(default 1), pageSize (default 50, max 200).
{
"workspaceId": "8b1e...",
"windowDays": 30,
"searchConsole": { "connected": true, "latestDataDate": "2026-08-24" },
"data": [
{
"query": "best billing software for saas",
"impressions": 3120,
"clicks": 74,
"ctrPercent": 2.4,
"avgPosition": 8.4,
"isPromptLike": false,
"isLongTail": true
}
],
"page": 1,
"pageSize": 50,
"total": 1840
}
Notes:
searchConsolecarries the same connection and freshness contract asanalyticsabove, including the stalled-sync case, and an emptydataarray means "nothing measured", never "no search demand".totalcounts distinct queries in the window, which is whatpagewalks.avgPositionis weighted by impressions, the way Search Console computes its own average position, so the number matches the one in your Search Console export for the same window. It isnullfor queries whose rows all predate position collection.- Offset paging stops at 2,000 queries per window. Narrow
windowDaysto bring the set inside the cap;totalkeeps reporting the full count so a truncated walk is never silent. - Queries Google suppresses for privacy never reach Search Console's API, and so never reach this endpoint either.
Typical warehouse sync
# Nightly: pull citations incrementally from a stored checkpoint
curl -s "https://app.honeyb.ai/api/v1/citations?since=$LAST_SYNC&pageSize=200" \
-H "Authorization: Bearer $HONEYB_API_KEY"
# ...follow nextCursor until null, then store the new checkpoint.
# Nightly: pull yesterday's results
curl -s "https://app.honeyb.ai/api/v1/results?since=$YESTERDAY&pageSize=200" \
-H "Authorization: Bearer $HONEYB_API_KEY"
# Daily headline metrics
curl -s "https://app.honeyb.ai/api/v1/visibility" \
-H "Authorization: Bearer $HONEYB_API_KEY"
# Daily: AI-attributed traffic, and the search demand behind it
curl -s "https://app.honeyb.ai/api/v1/web-traffic?windowDays=28" \
-H "Authorization: Bearer $HONEYB_API_KEY"
curl -s "https://app.honeyb.ai/api/v1/search-queries?windowDays=28&pageSize=200" \
-H "Authorization: Bearer $HONEYB_API_KEY"
Data freshness and history
Analyses run daily per engine; sync after ~07:00 UTC to capture the full previous cycle. Your history is retained in full for the life of your contract and accumulates from the day tracking starts: the earlier your prompt set is live, the deeper your dataset at every renewal.
The two traffic endpoints are the exception to that unbounded history.
Analytics and Search Console data is re-synced nightly as a trailing 90-day
window, so /web-traffic and /search-queries serve the last 90 days
only. Warehouse the daily pulls yourself if you want traffic history that
outlives that window, and use latestDataDate to skip a pull that would
land on a sync that has not run yet.
Frequently asked
Can our AI assistants query this directly? An MCP endpoint (for Claude, ChatGPT, Cursor, and other assistants) built on the same data layer is on our near-term roadmap; ask us about early access.
What about exports instead of polling? For warehouse-first teams we offer scheduled exports (NDJSON/CSV to object storage) and can discuss warehouse-native sharing; tell us your stack.
Get access
The API ships under enterprise agreements alongside onboarding support: a prompt-set design session, named-competitor curation, and a shared Slack channel during integration. Email hello@honeyb.ai with a sentence about what you're building and we'll take it from there.