Skip to content

Add public API reference for Inspector issue endpoints - #1740

Merged
bjornj12 merged 11 commits into
mainfrom
docs/inspector-issues-api-reference
Aug 26, 2026
Merged

Add public API reference for Inspector issue endpoints#1740
bjornj12 merged 11 commits into
mainfrom
docs/inspector-issues-api-reference

Conversation

@bjornj12

@bjornj12 bjornj12 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Adds pages/reference/public-api/inspector-issues.mdx, documenting the two Inspector issue endpoints, and registers it in _meta.js and the Public API overview.

Endpoint Returns
/workspaces/:workspaceId/inspector/issues/v3/:issueId a single issue, counts broken down per app version
/workspaces/:workspaceId/inspector/issues/:issueId/variations the event shapes behind an issue, JSON or CSV

Both are id-addressed: readers arrive with an issueId, which the Avo web app exposes in the URL of an open issue. The issue list endpoint (/inspector/issues/v5) is deliberately not documented — too heavy to expose publicly.

Service-account authentication only, matching the other public API pages. Every query parameter, response field and status code is documented, with a worked example per endpoint and the CSV format spec.

Merge ordering

This page states that both endpoints accept a service account credential. That is the post-fix state — today /issues/v3/:issueId rejects service-account Basic with a 401 and takes a Firebase ID token only. The auth change needs to ship before this merges.

Worth a reviewer's eye

The page went through several passes to strip internal detail and get to what someone needs to call these two endpoints. Two factual claims were caught and corrected during review, both from the same source of error — inferring runtime behavior from setup SQL and id-derivation code in the monorepo:

  • Data freshness. The page had claimed counts lag ~1h and that variations exclude the most recent hour. Wrong: the aggregates behind these endpoints are real-time, so recent data is unioned in from the raw table. Now states the real constraint, a few minutes of ingestion latency.
  • issueId stability. The page had warned against storing issueId. Wrong: an issue keeps its id, and a different problem creates a separate issue. Warning removed.

Remaining claims in that same class are worth confirming against a live workspace, since they're all observable from a real response: the 400-row cap and variationsTruncated, /variations returning the event across all sources when sourceId is omitted, the IgnoreIgnored naming difference, and the regression semantics.

Also unverified: whether /variations is deployed. A planning doc recorded it as not deployed as of 2026-08-19, though commits have landed on the handler since. If it isn't live, this documents an endpoint that 404s.

Things the page documents because they cause wrong integrations

  • eventCount is the total volume of that event on that source, healthy occurrences included — not "events affected". issueCount is the violating part; the ratio is what matters.
  • Without ?sourceId=, /variations returns the event across every source in the workspace, not just the issue's source.
  • variationsTruncated: true means the 400-row cap was hit; narrow with sourceId and appVersion.
  • Variation property names are what the SDK actually sent, not tracking-plan names.
  • Nothing in either response identifies which event variant Inspector matched against.

cspell and next lint pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa

Documents the three GET endpoints on api.avo.app that expose Inspector
data outside the web app: the issue list (/inspector/issues/v5), a single
issue (/inspector/issues/v3/{issueId}), and event-shape variations
(/inspector/issues/{issueId}/variations).

Written for an engineer or agent developer integrating with a service
account or OAuth token rather than for a web-app user, so every query
parameter, response field, status code and silent fallback is spelled
out, with working curl examples.

Covers the semantics that cause wrong integrations when assumed away:
the 24h count-gated window and the three meanings of an empty array
(including HTTP 200 on a database failure), eventCount vs issueCount,
issueId instability vs sharedIssueId, the 400-row variations cap and
variationsTruncated, raw SDK property names, the materialization lag,
and the absence of variant attribution.

Verified against the implementation. Three points differ from the
original spec and are documented as the code behaves: auth is not
uniform (the /v3/ single-issue route takes a Firebase ID token only and
rejects service-account Basic and OAuth JWT), a foreign-workspace
credential gets 403 rather than 404, and sharedIssueId is insulated from
new observed types and source but not from tracking-plan edits in
general.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 26, 2026 10:16am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4487abee-1f30-47dc-a9ba-668a3c26f3c6

📥 Commits

Reviewing files that changed from the base of the PR and between 747815c and 540acb9.

📒 Files selected for processing (3)
  • pages/reference/public-api/_meta.js
  • pages/reference/public-api/inspector-issues.mdx
  • pages/reference/public-api/overview.mdx

Included review availability: 7 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.


📝 Walkthrough

Walkthrough

The public API reference now includes Inspector Issues documentation. It covers issue listing, single-issue retrieval, event variation retrieval, authentication, parameters, response formats, errors, limits, examples, and related resources.

Changes

Inspector Issues API

Layer / File(s) Summary
API contract and navigation
pages/reference/public-api/_meta.js, pages/reference/public-api/overview.mdx, pages/reference/public-api/inspector-issues.mdx
Adds the Inspector Issues navigation entry and documents shared endpoint behavior, authentication, workspace scoping, limits, identifiers, and error responses.
Issue listing and retrieval
pages/reference/public-api/inspector-issues.mdx
Documents issue listing and single-issue retrieval, including parameters, authentication, response fields, status semantics, time windows, failures, and examples.
Event variation retrieval
pages/reference/public-api/inspector-issues.mdx
Documents event variation filtering, truncation, CSV decoding, response fields, type handling, examples, and related resources.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 540ac

This PR adds API documentation and navigation entries only. No actionable merge-blocking risk remains beyond normal review and checks.

Poem

A rabbit reads the endpoints bright,
Three Inspector paths now show their light.
Issues hop in ordered rows,
Variations bloom where data flows.
Clear requests guide each little nose.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding public API reference documentation for Inspector issue endpoints.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/inspector-issues-api-reference

Comment @coderabbitai help to get the list of available commands.

@logason

logason commented Aug 25, 2026

Copy link
Copy Markdown
Member

The list endpoint (/inspector/issues/v5) is too heavy to expose as a
public API, so it comes out of the docs entirely. The page now covers
only the two id-addressed endpoints: a single issue
(/inspector/issues/v3/{issueId}) and event-shape variations
(/inspector/issues/{issueId}/variations). Readers arrive with an
issueId, which the Avo web app exposes in the URL of an open issue.

The single-issue response was previously documented as a diff against
the list endpoint's field table, so that documentation moves into the
single-issue section and is now self-contained: the full field table,
the issueType and issueStatus unions, and the regression semantics.

Two cross-cutting warnings were specific to the list endpoint and go
with it: the 24h count-gated window with the three meanings of an empty
array, and the silent HTTP 200 with an empty array on a database
failure. Both remaining endpoints propagate a database failure as a 500,
so that trap no longer exists on this page.

Sections are renamed off the A/B/C labels now that only two endpoints
remain, and every in-page anchor was rewired to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
The /inspector/issues/{issueId} route (without /v3/) resolves a
sharedIssueId rather than an issueId. Rather than document it as a
neighbouring resource, the page now simply states the single-issue path
as /inspector/issues/v3/{issueId} and tells readers to use it exactly as
written.

Removes the "Watch the path" section and rewords the two cross
references that pointed at it. The sharedIssueId response field stays
documented, since it is still returned in the body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
bjornj12 and others added 2 commits August 25, 2026 10:23
Both endpoints now accept service account Basic, Avo OAuth JWT and a
Firebase ID token. Removes the credential matrix, the "Firebase ID token
only" warnings on the single-issue endpoint, and the framing that steered
service-account integrations away from it.

The single-issue endpoint now shares the Avo API authenticator, so its
auth failures use the shared {"message": ...} bodies and a non-member
gets 403 rather than the previous 401 {"error": "Unauthorized"}.

Depends on the auth change shipping first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
Drops the "never count rows" warning about undecodable rows making a
truncated page look complete. The flag is now described plainly where the
cap is explained: variationsTruncated: true means the response reached
the 400-row cap, and sourceId / appVersion are how you narrow the query
to get back under it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa

_Read Inspector issues and observed event shapes over HTTP_

Two GET endpoints expose Inspector data outside the Avo web app: a single issue, and the observed event shapes ("variations") behind an issue. Both are addressed by an `issueId` you already hold, and both share a base URL, an authentication model and a workspace-scoping model.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • skip "outside the avo web app"
  • how does the user already hold an issueId?
  • an authentication model and a workspace-scoping sounds like internal jargon

@@ -0,0 +1,472 @@
import { Callout } from 'nextra/components';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a iteration pass on this doc keeping in mind its a public doc that doesn't require any internal-only tech details. Only focus on what the user must know to be able to use these two endpoints effectively. Make sure text is succinct and easily parseable. Skip any non-essential information.


Two GET endpoints expose Inspector data outside the Avo web app: a single issue, and the observed event shapes ("variations") behind an issue. Both are addressed by an `issueId` you already hold, and both share a base URL, an authentication model and a workspace-scoping model.

This page is written for someone wiring these endpoints into a script, a CI check, or an agent tool. The response body is your only view of the data, so every field, fallback and silent behavior is spelled out below.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skip?


This page is written for someone wiring these endpoints into a script, a CI check, or an agent tool. The response body is your only view of the data, so every field, fallback and silent behavior is spelled out below.

Base URL for both: `https://api.avo.app`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base URL for Avo public API


| Method and path | Returns | Reach for it when |
| --- | --- | --- |
| `GET /workspaces/:workspaceId/inspector/issues/v3/:issueId` | A **single issue** — a bare object | You need per-app-version counts for one issue, or a window other than 24 hours. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skip "a bare object"

Comment on lines +32 to +38
- **Service account Basic** — `Authorization: Basic base64(name:secret)`
- **Avo OAuth JWT** — `Authorization: Bearer ...`
- **Firebase ID token** — `Authorization: Bearer ...`

Neither endpoint requires an OAuth scope, and neither requires a particular workspace role — any workspace member passes.

See [authorization header](/public-api/authentication#authenticating-with-avo-api) for how to build the Basic credential from a service account name and secret. The `Basic ` scheme is matched **case-sensitively**, so a lowercase `basic ` is not recognized as a service-account credential — it is treated as a malformed Bearer token and rejected with the message below.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focus only on service account auth here (mirror other public api docs)?

| `401` | `{"message": "Invalid authorization"}` | Any Basic failure: bad secret, unknown service account, or a service account not registered in this workspace. |
| `403` | `{"message": "Access denied to workspace"}` | A verified Bearer identity that is not a member of `:workspaceId`. |

A service account is never checked against the workspace ACL — its only workspace binding is the account record living under that workspace — so a service account can never produce the 403.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too internal tech specific (e.g. "workspace ACL")


### Workspace scoping

Every query filters on `schema_id`, so a credential can only ever see its own workspace's rows. That produces two different failures that are easy to confuse:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too internal tech specific

Comment on lines +40 to +60
### Authentication error bodies

Both endpoints run on the shared Avo API authenticator, which returns these four bodies. All of them use a `message` key, unlike the `error` key the endpoints themselves use for 400/404/500.

| Code | Body | Condition |
| --- | --- | --- |
| `401` | `{"message": "Authorization header missing"}` | No `Authorization` header at all. |
| `401` | `{"message": "Authorization header missing or invalid"}` | Unrecognized scheme, empty Bearer token, or any Bearer verification failure — an expired, revoked or wrong-project Firebase token and an invalid Avo OAuth JWT are indistinguishable here. |
| `401` | `{"message": "Invalid authorization"}` | Any Basic failure: bad secret, unknown service account, or a service account not registered in this workspace. |
| `403` | `{"message": "Access denied to workspace"}` | A verified Bearer identity that is not a member of `:workspaceId`. |

A service account is never checked against the workspace ACL — its only workspace binding is the account record living under that workspace — so a service account can never produce the 403.

### Workspace scoping

Every query filters on `schema_id`, so a credential can only ever see its own workspace's rows. That produces two different failures that are easy to confuse:

- **403 `{"message": "Access denied to workspace"}`** — the Bearer credential is valid, but its user is not in the ACL for `:workspaceId`. An unknown `:workspaceId` returns the same 403, because there is no ACL document to match against. A Basic credential whose service account is not registered in that workspace returns **401 `{"message": "Invalid authorization"}`** instead.
- **404** — the credential is valid *and* scoped to the right workspace, but the requested id isn't in that workspace's rows. Because the lookup is workspace-scoped (`schema_id = $1 AND issue_id = $2`), an id belonging to a different workspace simply doesn't match and returns 404 rather than revealing that the id exists elsewhere.

So a 403 means "wrong workspace credential" and a 404 means "right credential, id not here" — including the case where the id is real but lives in someone else's workspace. Super-admin credentials bypass both checks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel like we can compact this a lot


### Rate limits

There is no rate limit on either of these endpoints.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we still state some rate limits in case we need to add them in the future?

Addresses review feedback that the page read like internal engineering
notes rather than public API documentation.

Documents service account authentication only, matching the other public
API pages. Because a service account cannot hit the workspace-membership
case, the 403 responses and the Bearer-specific error body go with it,
and workspace scoping compresses to a single sentence.

Cuts internal detail throughout: query filters, workspace ACLs,
continuous aggregates and refresh policies, id hash formulas, storage
mechanics, and decode behavior. Where one of those explained a real rule,
the rule stays in plain terms without the machinery.

Also per review: drops the /v3/ path callout rather than explaining the
version-segment difference, answers where an issueId comes from in the
opening paragraph, generalizes the variations use case, and states that
no rate limit is enforced today but one may be introduced.

Prose is down about 28%. All parameters, response fields, status codes,
the CSV format and both examples are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
The previous wording led with "no rate limit is currently enforced",
which invites integrations designed around unlimited throughput that
break the day a limit is introduced.

Now leads with the expectation instead: treat the endpoints as rate
limited, design against the 1 request per second per service account
that the rest of the Avo public API soft-enforces, retry on 429 with
exponential backoff and Retry-After, and reach out for higher sustained
volume. Does not claim a limit is enforced on these endpoints today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
The page read as over-polished: em dashes used for dramatic asides,
balanced "X is not Y, Z is the W" constructions, and sentences that
editorialized about the significance of the fact just stated.

Rewrites the prose in shorter, flatter sentences that say the thing and
stop. Em dashes drop from 20 to 7, mostly in table cells. Section
headings lose the antithesis phrasing. No facts, field names, parameters,
status codes or examples change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
Second pass on the review note that this is a public doc and should carry
only what someone needs to call these two endpoints.

Structural: drops the "Your first call" section, which duplicated the
per-endpoint examples, and the "Before you integrate" preamble that sat
between the reader and the reference. Its four warnings now appear as
short callouts in the sections they apply to, so the endpoint you came
for is the first thing after authentication.

Trimmed: the regression validateIn table down to two sentences, the CSV
section by about half, the status code tables, and the field table notes.
Removed the parameter regex, the error-string casing asides, the request
ordering note on the 400, and the internal derivation of eventKey and
sourceKey, which are now one line saying they can be ignored.

415 lines to 319, with no em dashes left. All parameters, response
fields, status codes and both examples are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
bjornj12 and others added 2 commits August 26, 2026 10:14
The page claimed counts lag by about an hour and that variations exclude
the most recent hour entirely. That is wrong: the aggregates behind these
endpoints are real-time, so recent data is included rather than hidden
until it is precomputed.

Replaces it with the actual constraint, which is ingestion latency of a
few minutes, and keeps the pointer to the Inspector Debugger for
verifying an implementation as you ship it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa
The claim that issueId changes and shouldn't be stored was wrong. An
issue keeps its issueId; a different problem produces a separate issue
with its own id.

Drops the callout and the "not stable" note in the field table, and
describes sharedIssueId by what it does rather than by contrast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa

@logason logason left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@bjornj12
bjornj12 merged commit aaa8020 into main Aug 26, 2026
4 checks passed
@bjornj12
bjornj12 deleted the docs/inspector-issues-api-reference branch August 26, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants