Add public API reference for Inspector issue endpoints - #1740
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
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. 📝 WalkthroughWalkthroughThe 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. ChangesInspector Issues API
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to This PR adds API documentation and navigation entries only. No actionable merge-blocking risk remains beyond normal review and checks. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Docstring CoverageExplanation 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
🧪 Generate unit tests (beta)
Comment |
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
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. |
There was a problem hiding this comment.
- skip "outside the avo web app"
- how does the user already hold an issueId?
an authentication model and a workspace-scopingsounds like internal jargon
| @@ -0,0 +1,472 @@ | |||
| import { Callout } from 'nextra/components'; | |||
There was a problem hiding this comment.
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. |
|
|
||
| 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` |
|
|
||
| | 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. | |
| - **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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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: |
| ### 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. |
There was a problem hiding this comment.
feel like we can compact this a lot
|
|
||
| ### Rate limits | ||
|
|
||
| There is no rate limit on either of these endpoints. |
There was a problem hiding this comment.
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
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



Adds
pages/reference/public-api/inspector-issues.mdx, documenting the two Inspector issue endpoints, and registers it in_meta.jsand the Public API overview./workspaces/:workspaceId/inspector/issues/v3/:issueId/workspaces/:workspaceId/inspector/issues/:issueId/variationsBoth 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/:issueIdrejects 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:
issueIdstability. The page had warned against storingissueId. 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,/variationsreturning the event across all sources whensourceIdis omitted, the Ignore →Ignorednaming difference, and theregressionsemantics.Also unverified: whether
/variationsis 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
eventCountis the total volume of that event on that source, healthy occurrences included — not "events affected".issueCountis the violating part; the ratio is what matters.?sourceId=,/variationsreturns the event across every source in the workspace, not just the issue's source.variationsTruncated: truemeans the 400-row cap was hit; narrow withsourceIdandappVersion.cspell and
next lintpass.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQppoqtYPWnv1c5m2XvPVa