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/v1 is 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:

StatusCodeMeaning
400invalid_queryA query parameter is malformed, or a date window ends at or before it starts
401unauthorizedMissing, 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)
429rate_limitedOver the request budget; back off and retry
500internal_errorOur 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, /results and /search-queries. Offset pagination stops at 2,000 rows per filter window. On /results, follow nextCursor to read past it; on /search-queries, narrow windowDays (/prompts has no window and typical prompt sets sit well inside the cap).
  • Keyset (cursor) on /results and /citations: pass the previous response's nextCursor back as cursor until it returns null. It has no depth limit. On /citations it is the recommended way to sync incrementally; persist your last since checkpoint between syncs.

Date windows

/results and /citations take since (inclusive) and until (exclusive), both ISO 8601. Without since the window is the 90 days before until; without until it ends now. Because the end is exclusive, adjacent windows share a boundary without sharing a row: this week is since=2026-10-19T00:00:00Z&until=2026-10-26T00:00:00Z, last week since=2026-10-12T00:00:00Z&until=2026-10-19T00:00:00Z. A date with no time (2026-10-19) reads as midnight UTC.

Endpoints

EndpointWhat it returns
GET /api/v1/visibilityVisibility summary + up to 365 days of daily history
GET /api/v1/promptsThe tracked prompt set (topic, intent, search volume)
GET /api/v1/resultsPrompt-level AI answers, one row per prompt × engine × run
GET /api/v1/citationsEvery URL cited in your answers, built for incremental sync
GET /api/v1/competitorsShare of voice per brand observed in your answers
GET /api/v1/web-trafficSessions and conversions with the AI-attributed slice (Google Analytics) and Bing search (Bing Webmaster Tools)
GET /api/v1/search-queriesSearch 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. The latest* fields cover the most recent run of each engine the workspace tracks; an engine you stop tracking drops out of them once a tracked engine has a run. 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, until (see Date windows), cursor (opaque, from the previous nextCursor; with it, page is ignored).

{
  "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,
  "nextCursor": "Mnw1MHw4MTJ8MTc3..."
}

Notes:

  • answer is 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.
  • page stops at 2,000 rows into the window; nextCursor does not. Start at page 1 and follow nextCursor to walk the whole window. nextCursor is null on the last page.
  • A cursor keeps the window and pageSize of the page that issued it, so a walk covers one fixed window even when it has no since and runs for hours. With a cursor you can leave since, until and pageSize off, or repeat them as they were (appending cursor to your first query works); a different value is a different query and returns invalid_query, as does a cursor sent with another workspace's key.
  • Every answer in the window when the walk began is returned exactly once. An answer that lands during the walk may or may not be in it, and total is the count when the walk began, so it can differ from the rows you receive by those few. De-duplicate on id across syncs.
  • engine is one of the stable slugs chatgpt, 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 total for 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. Each row names the answer that cited it (resultId, a row of /results), that answer's promptId, topic and engine, so citations group by topic or engine without a second call. topic is the prompt's topic when the answer ran (a later rename does not rewrite past rows), or null when it had none. pageClassification says what the page is (listicle, comparison, how_to, article, product_page, category_page, homepage, other), or is null when the page is not classified.

Query parameters: pageSize (default 50, max 200), since, until (see Date windows), cursor (opaque, from the previous nextCursor).

{
  "data": [
    {
      "id": "d81f...",
      "resultId": "b2c4...",
      "promptId": "3f9a...",
      "topic": "Enterprise Billing",
      "engine": "chatgpt",
      "url": "https://example.com/best-billing-software",
      "domain": "example.com",
      "pageType": "other",
      "pageClassification": "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.

To compare two periods (citations by topic or engine, this week against last), walk each window with since and until and count rows by topic or engine.

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 shares count every answer in the window: scannedResults is the number of answers counted, and isTruncated is always false. A 365-day window therefore weighs every month of the year, not just the latest days.

Mention counts follow your dashboard's rules exactly. Each brand is labelled with the spelling that carried the most mentions, so the label can differ in casing from the dashboard's. Brands with equal mentions are listed alphabetically, which also decides which of them make the 50-brand cut. A response is reused for up to an hour per workspace and windowDays, so answers from the last hour may not be counted yet.

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.

Bing search sits beside the Google Analytics figures as bingSearch, null until a Bing Webmaster Tools site is connected under Web traffic, Search queries, Bing, whatever Google Analytics' state.

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,
    "aiEarnedSessions": 1310,
    "aiPaidSessions": 420,
    "aiUnclassifiedSessions": 110,
    "aiEarnedConversions": 33,
    "aiPaidConversions": 11,
    "aiUnclassifiedConversions": 3,
    "aiNonPaidSessions": 1420,
    "aiNonPaidConversions": 36,
    "aiNonPaidSharePercent": 2.9
  },
  "topAiLandingPages": [
    { "landingPage": "/pricing", "sessions": 412, "conversions": 19, "paidSessions": 0 }
  ],
  "topAiSources": [
    { "source": "chatgpt.com", "sessions": 980, "paidSessions": 0 },
    { "source": "openai_ad", "sessions": 420, "paidSessions": 420 }
  ],
  "bingSearch": {
    "latestDataDate": "2026-08-22",
    "totals": { "clicks": 1204, "impressions": 48210, "ctrPercent": 2.5, "promptLikeQueries": 37 },
    "topQueries": [
      {
        "query": "how to bill by usage",
        "impressions": 900,
        "clicks": 27,
        "ctrPercent": 3,
        "avgPosition": 4.3,
        "isPromptLike": true,
        "isLongTail": true
      }
    ]
  }
}

Notes:

  • totals is null, never zeroes, whenever nothing was measured in the window, and analytics tells you which case you are in: connected: false means no property is connected; connected: true with a null latestDataDate means the first sync has not landed; a latestDataDate older 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".
  • aiSessions counts every arrival from an assistant, including clicks on ads bought inside one (ChatGPT ads arrive as openai_ad). For AI visibility, read aiNonPaidSessions and aiNonPaidSharePercent: every assistant visit not identified as a bought ad, which is aiSessions minus aiPaidSessions. It breaks down into aiEarnedSessions, the visits Google Analytics confirmed were not paid, and aiUnclassifiedSessions, assistant visits it did not label either way (a ChatGPT link tagged with a source but no medium lands in GA4's Unassigned channel). Count unclassified visits as visibility, never as ad spend. aiPaidSessions is ad spend. Earned, paid and unclassified always add up to aiSessions, and paidSessions on each source and landing page shows where the bought part came from and where it landed.
  • aiSessions is 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.
  • latestDataDate is 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, totals goes back to null rather than reporting the zero that partial data would imply. Reconnect the property in the app if it stays stale.
  • windowDays is capped at 90 because 90 days is the window we keep synced. A larger value comes back clamped, with the clamped number echoed in windowDays.
  • bingSearch is Bing's twin of Search Console: clicks and impressions over the same window, from Bing's daily totals, the distinct question-shaped queries (promptLikeQueries), and the 10 queries with the most impressions, in the /search-queries row shape. It is null when no Bing site is connected. Connected with nothing measured in the window gives totals: null, read the same way as Google Analytics' totals: a null latestDataDate means no data has landed yet (the first sync has not finished, or Bing has returned nothing for the site so far, as it does for about 48 hours after a site is added, and for good for a site with no Bing searches), an older one means the sync has stopped. Bing reports with a delay of a few days. Its query report refreshes weekly, behind the daily totals, so the query figures can cover fewer days than the totals; when it lists no queries for a window the totals cover (it has not reached the window yet, or lists none of the site's searches), promptLikeQueries is null, never 0, and topQueries is empty.
  • Bing's AI Performance report (Microsoft Copilot citing your site) has no API yet, so it is not served. Copilot visits usually arrive as bing.com or with no referrer, so Google Analytics counts them under Search or Direct, not in aiSessions.

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:

  • searchConsole carries the same connection and freshness contract as analytics above, including the stalled-sync case, and an empty data array means "nothing measured", never "no search demand".
  • total counts distinct queries in the window, which is what page walks.
  • avgPosition is 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 is null for queries whose rows all predate position collection.
  • Offset paging stops at 2,000 queries per window. Narrow windowDays to bring the set inside the cap; total keeps 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, Search Console and Bing Webmaster Tools 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.