Inspector Issues
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.
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
Endpoints
| 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. |
GET /workspaces/:workspaceId/inspector/issues/:issueId/variations | The event shapes behind an issue, as JSON or CSV | You need the payloads themselves — which property names and types were actually sent — so you can diff the shape causing the issue against the healthy one. |
:workspaceId is the ID of your workspace. You’ll find it in the URL of your Avo tab after /schemas/. It is also returned as schemaId on every response object.
Both endpoints take an issueId. You’ll find an issue’s id in the Avo web app URL when you open that issue: https://www.avo.app/schemas/{workspaceId}/inspector/issues/ii/{issueId}.
Use the /v3/ path exactly as written. The single-issue endpoint is /inspector/issues/v3/:issueId. Dropping the /v3/ segment does not reach this endpoint.
Authentication
Both endpoints accept the same three credentials:
- 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 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.
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:workspaceIdreturns 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.
Rate limits
There is no rate limit on either of these endpoints.
Your first call
Once you have a credential and an issueId, the event shapes behind that issue are a single request:
$ curl -H "authorization: Basic <Base64 encoded token>" \
-X GET "https://api.avo.app/workspaces/:workspaceId/inspector/issues/:issueId/variations"That returns {"variations": [...], "variationsTruncated": false} — every shape that event was seen in over the last 24 hours, each flagged with whether it is one of the shapes causing the issue. From there you can:
- Narrow the response to one source and one app version, or switch it to CSV — see listing event variations.
- Read the issue’s own counts broken down per app version with the single-issue endpoint.
Now that you have a working call, the sections below cover what the response does not tell you.
Before you integrate
Six behaviors are not visible anywhere in the response body, and each one produces a plausible-looking but wrong integration when it is assumed away. Four of them cut across both endpoints and are covered here:
- The time windows are fixed, and the freshest hour is missing.
eventCountis not “events affected by this issue”.issueIdis a snapshot handle, not a durable key.- No response tells you which event variant was matched.
Two more are specific to the variations endpoint and are covered in its own section: variationsTruncated is the only reliable completeness signal, and property names are the raw names the SDK sent, not tracking-plan names.
The time windows are fixed, and the freshest hour is missing
The variations endpoint looks back a fixed 24 hours. That window is a literal in the query, with no parameter to widen or shift it. The single-issue endpoint is the one that takes a window, via its time parameter.
Expect roughly an hour of lag, and do not use these endpoints to verify a deploy you just shipped.
The counts are read from continuous aggregates refreshed on a ten-minute schedule with a one-hour end offset, which puts roughly an hour of lag on the freshest numbers. On the variations endpoint the aggregate is materialized-only, so the most recent hour is not visible at all — a deploy 20 minutes old shows nothing there. If you are validating an implementation as you ship it, use the Inspector Debugger rather than these endpoints.
Looking further back is not an option on the variations endpoint either: both the aggregate and the raw table drop data after 48 hours, so the 24-hour window is always fully covered and there is nothing older to read.
eventCount is not “events affected by this issue”
eventCount is the total volume of that event on that source in the window — every shape, healthy ones included. issueCount is the per-issue figure: occurrences in the same window that actually violated.
The number worth reporting is the ratio. issueCount: 1428 against eventCount: 96204 is a 1.5% violation rate on a high-volume event; reading eventCount as “affected events” overstates the blast radius by two orders of magnitude.
issueId is a snapshot handle; sharedIssueId is the identity
issueId is sha256(schemaId : sourceId : eventName : propertyName : issueType payload) — the full encoded issueType payload is hashed.
issueId is not stable. It changes when a tracking-plan edit moves a propertyId, eventId or expectedPropertyType inside the payload, and when a newly observed runtime type is appended to an InconsistentType issue’s propertyTypes. Because issue_id is the primary key of the issues table, a changed hash creates a new row: the old issue is orphaned with its original firstSeen, and the new one starts fresh with no history. Treat issueId as a handle valid within one response or one session — safe to pass straight to /variations, not safe to persist as a long-lived key in your own database.
sharedIssueId is sha256(schemaId : eventName : propertyName : issueType), with sourceId omitted — that omission is what groups one logical problem across several sources. For InconsistentType the volatile propertyTypes array is deliberately excluded from the hash as well.
That stability only goes so far, though. sharedIssueId is insulated from newly observed types and from source, but it is not immune to tracking-plan edits in general. For the five issue types other than InconsistentType it still hashes propertyId / eventId / expectedPropertyType, so a tracking-plan edit moves both ids. Only InconsistentType is fully insulated.
No variant attribution
Nothing in any response — JSON or CSV — tells you which event variant Inspector matched against. There is no variant field in any of these payloads, no variant column in the underlying tables, and variant is not an input to either id hash. The tracking-plan model reaches the matcher already flattened, so variant identity is erased before validation and never reaches the issue row. It cannot be recovered from the response or from the id. If your tracking plan leans on variants, expect to reconcile variant identity yourself.
Retrieving a single issue
GET https://api.avo.app/workspaces/:workspaceId/inspector/issues/v3/:issueIdReturns one issue with its counts broken down per app version, over a window you choose. This is the endpoint to reach for when you need to know which release a problem is concentrated in, or when 24 hours is the wrong window.
:issueId is the issue’s own id, the value returned as issueId in the response below.
Query parameters
| Parameter | Type | Required | Default when omitted | Accepted values | On invalid input |
|---|---|---|---|---|---|
time | string | Optional | 24h | Matches ^(\d+)([hd])$, case-insensitive — for example 12h, 7d, 30D | Silently coerced to 24 hours. No 400. |
time also selects the underlying rollup: 24h reads the eight-hour aggregates, anything else reads the daily aggregate tables. The value is regex-sanitized before use. There is no format, no filtering and no pagination on this endpoint.
Response
A bare object, not wrapped in an envelope, and not gzipped.
| Field | Type | Notes |
|---|---|---|
issueId | string, never null | sha256 hex. See snapshot handle above. |
sharedIssueId | string, never null | sha256 hex. Stable identity across sources. |
schemaId | string | Your workspace ID. |
sourceId | string | A single source — an issue row is per-source. |
eventName | string | The event name as observed. |
propertyName | string | null | null for event-level issue types. |
issueType | object | Tagged union, see below. |
oldestAppVersion | string | |
newestAppVersion | string | |
firstSeen | string (ISO 8601) | Earliest first-seen for this issue row. |
lastSeen | string (ISO 8601) | Max last-seen across versions, falling back to the last-seen day. |
issueCount | number | Occurrences that violated, summed across versions. |
eventCount | number | Total occurrences of that event on that source, all shapes including healthy ones, summed across versions. |
appVersions | object | A dictionary keyed by version string, not an array. Each value is {"appVersion": string, "issueCount": number, "eventCount": number, "lastSeen": string | null}. |
issueStatus | object | {status, updatedAt: string | null, updatedBy: string | null} |
regression | boolean | Always present. true when this issue had been marked Resolved and was then observed again — see below. |
branchIds | string[] | Always present; [] when the issue is not linked to any branch. |
Top-level issueCount and eventCount are the sums across versions; top-level lastSeen is the max across versions, falling back to the last-seen day.
issueType
A tagged union: type plus a payload key. The concepts behind each type are documented in issue types in Inspector.
{ "type": "EventNotInTrackingPlan" }
{ "type": "UnexpectedEvent" }
{ "type": "MissingExpectedProperty", "missingExpectedProperty": { "eventId": "...", "propertyId": "...", "propertyName": "..." } }
{ "type": "PropertyTypeInconsistentWithTrackingPlan", "PropertyTypeInconsistentWithTrackingPlan": { "eventId": "..." , "propertyId": "...", "propertyName": "...", "expectedPropertyType": "...", "actualPropertyType": "..." } }
{ "type": "UnexpectedProperty", "unexpectedProperty": { "eventId": "...", "propertyName": "...", "propertyType": "..." } }
{ "type": "InconsistentType", "inconsistentType": { "propertyName": "...", "propertyTypes": ["string", "int"] } }Casing inconsistency to code around: every payload key is camelCase except PropertyTypeInconsistentWithTrackingPlan, whose payload key repeats the PascalCase type name. eventId inside that payload is nullable; the other payloads’ ids are not.
issueStatus.status
{ "type": "Unresolved" }
{ "type": "Ignored", "validateIn": { "type": "NextAppVersion", "appVersion": "8.15.0" } }
{ "type": "Resolved", "validateIn": { "type": "Never" } }validateIn is one of {"type":"CurrentAppVersion","appVersion":string}, {"type":"NextAppVersion","appVersion":string}, {"type":"CustomAppVersion","appVersion":string}, {"type":"Date","date":ISO 8601} or {"type":"Never"}.
Note the naming shift between the label you set in the Avo web app and the value you read back:
| Avo web app label | issueStatus.status.type |
|---|---|
| Unresolved | Unresolved |
| Ignore | Ignored |
| Resolved | Resolved |
An issue that has never had a status set reads as Unresolved. See issue status for what each one means.
regression
regression is set to true when an issue a user had marked Resolved is observed again past the point at which it was supposed to be fixed. Inspector then moves the issue back to Unresolved and flags it. “Past the point it was supposed to be fixed” is exactly the validateIn recorded on the resolution:
validateIn | Regresses when the newly observed variation is |
|---|---|
CurrentAppVersion(v) | on app version ≥ v |
CustomAppVersion(v) | on app version ≥ v |
NextAppVersion(v) | on app version strictly > v |
Date(t) | seen after t |
Never | never — the issue is not reopened and never flagged |
Two things to code around:
Ignoreddoes not produce a regression. An ignored issue that resurfaces is also moved back toUnresolved, butregressionstaysfalse. OnlyResolvedsets it.- The flag is cleared the moment anyone sets the status manually again, to any value. A newly created issue is never a regression.
Read regression together with issueStatus.status: the Avo web app only surfaces it while the status is Unresolved, which is the only state it is meaningful in.
Status codes
| Code | Body | Condition |
|---|---|---|
200 | The issue object | At least one row matched. |
401 | See authentication error bodies | Missing or invalid credential. |
403 | {"message": "Access denied to workspace"} | A verified Bearer identity that is not a member of :workspaceId. |
404 | {"error": "Issue Not found"} | Zero rows for this workspace and id. Covers an unknown id, a malformed id, and an id belonging to a different workspace. Note the exact casing. |
500 | {"error": "Internal Server Error"} | Database error. |
There is no 400 on this endpoint.
Example
Request
$ curl -H "authorization: Basic <Base64 encoded token>" \
-X GET "https://api.avo.app/workspaces/hAtPI0dEsq/inspector/issues/v3/2f1c9b8e4d7a05c3e6b1a94f8d2c70b5e93a17d4c8f0b62a5d1e7c3948fb0a26?time=7d"Response
{
"issueId": "2f1c9b8e4d7a05c3e6b1a94f8d2c70b5e93a17d4c8f0b62a5d1e7c3948fb0a26",
"sharedIssueId": "8b4d0f6a1c93e57204ab8d1f6e3c9057b24da8f1093c6e5b7d20a41fc8e93b56",
"schemaId": "hAtPI0dEsq",
"sourceId": "9Zq7YAo0R",
"eventName": "Checkout Completed",
"propertyName": "revenue",
"issueType": {
"type": "PropertyTypeInconsistentWithTrackingPlan",
"PropertyTypeInconsistentWithTrackingPlan": {
"eventId": "yT2rKpQ4Xa",
"propertyId": "Bv8nLm1Zq0",
"propertyName": "revenue",
"expectedPropertyType": "float",
"actualPropertyType": "string"
}
},
"oldestAppVersion": "8.13.1",
"newestAppVersion": "8.14.2",
"firstSeen": "2026-08-11T09:42:18.000Z",
"lastSeen": "2026-08-24T06:00:00.000Z",
"issueCount": 9871,
"eventCount": 644390,
"appVersions": {
"8.13.1": {
"appVersion": "8.13.1",
"issueCount": 7204,
"eventCount": 402118,
"lastSeen": "2026-08-23T21:00:00.000Z"
},
"8.14.2": {
"appVersion": "8.14.2",
"issueCount": 2667,
"eventCount": 242272,
"lastSeen": "2026-08-24T06:00:00.000Z"
}
},
"issueStatus": {
"status": { "type": "Unresolved" },
"updatedAt": null,
"updatedBy": null
},
"regression": false,
"branchIds": []
}Listing event variations
GET https://api.avo.app/workspaces/:workspaceId/inspector/issues/:issueId/variationsA variation is one observed shape of an event: a distinct combination of property names and property types, per app version, per source. The single-issue endpoint tells you that an event is wrong and how often; this endpoint tells you how it is wrong, by returning every shape that event was seen in alongside a causingIssue flag and an occurrence count.
That is what makes it the debugging endpoint. Put the causing shape next to the healthy one and the diff — a property missing here, a type differing there, and the volume split between them — is usually the whole story. Available as JSON or, with ?format=csv, as a two-section CSV built for exactly that diff.
:issueId here is the same kind of id the single-issue endpoint takes.
Query parameters
| Parameter | Type | Required | Default when omitted | Accepted values | On invalid input |
|---|---|---|---|---|---|
format | string | Optional | json | csv, case-insensitive | Anything else — including "" and xml — returns JSON. Never errors. |
sourceId | string | Optional | No source filter | One exact source_id | Blank or whitespace means no filter. |
appVersion | string | Optional | No version filter | One exact app_version | Blank or whitespace means no filter. |
sourceId and appVersion take single values only. The filters are strict equality, so ?sourceId=a,b matches the literal string "a,b" and returns nothing. Repeating a parameter — ?sourceId=a&sourceId=b — arrives as an array, is parsed as absent, and the filter is silently ignored with no error. A non-string route parameter (for example a duplicated :issueId) returns 400 {"error": "Invalid request"} before authentication runs.
The issue’s own source is not applied as a filter. Without ?sourceId=, you get variations of that event name across every source in the workspace, not just the source the issue was reported on. If you want the issue’s own source, pass its sourceId explicitly.
Staying under the 400-row cap
The query is capped at 400 rows, and both app_version and source_id are grouping keys — so one logical event shape yields one row per app version per source. An event with modest shape diversity across several versions and sources reaches the cap on cardinality alone.
Ordering and the limit are applied in the database, before anything you could filter client-side, so the shape you care about may already have been cut from the response. Filtering after the fact does not recover it. Passing ?sourceId= and ?appVersion= — taking the sourceId from the issue itself — is the sanctioned way to stay under the cap.
Response
The envelope is {"variations": [...], "variationsTruncated": bool}. Each row has exactly these 13 fields:
| Field | Type | Notes |
|---|---|---|
eventVariationKey | string | Identifies this shape. sha256 hex of schemaId + sourceId + eventName + appVersion + propertyNameSignature + propertyTypeSignature, so it changes whenever any of those change. |
causingIssue | boolean | Whether this shape is one of the shapes causing the issue you asked about. |
count | number | Occurrences of this shape in the window. Sampling-adjusted, not a raw tally — the pipeline sums count / samplingRate and rounds, so on a sampled source this is an extrapolated estimate. Treat it as an estimate when comparing against counts from your own systems. |
eventName | string | The event name as observed. |
sourceId | string | The Avo Source ID. |
schemaId | string | Your workspace ID. |
appVersion | string | null | Nullable in the encoder, but always populated on this endpoint — a row with no app version fails to decode and is dropped. |
minCreatedAt | string | null | ISO 8601. Invalid or infinite timestamps emit null rather than throwing. |
maxCreatedAt | string | null | ISO 8601, same guard. |
eventKey | string | null | Not a tracking-plan ID. sha256 hex of schemaId + sourceId + eventName, computed from the observed event name. All variations of one observed name on one source share it. Nullable in the encoder, always populated here. |
sourceKey | string | null | Not the same value as sourceId. The composite schemaId + "-" + sourceId. Use sourceId for anything that has to match an Avo Source. Nullable in the encoder, always populated here. |
propertyNameSignature | string[] | Observed property names, sorted by name. |
propertyTypeSignature | string[] | Observed property types. Strictly parallel to propertyNameSignature — same length, same order, so propertyTypeSignature[i] is the type of propertyNameSignature[i]. Both are built by mapping one list of (name, type) pairs sorted by name, and an event whose types cannot be fully parsed is dropped rather than emitted with a short array. |
Read variationsTruncated, never count rows
Never compare variations.length to 400.
variationsTruncated is computed from the raw row count, but rows that fail to decode are dropped from the array you receive. So a truncated page can arrive with 399 rows and look complete. The flag is the only reliable completeness signal.
Property names are raw observed names
propertyNameSignature holds the names the SDK actually sent, not tracking-plan names. These come straight from the event payload; nothing in that path consults the Tracking Plan. The only mutation is privacy redaction of values shaped like data in a name position, which surfaces as the literals <Object redacted by Avo>, <ID string redacted by Avo> and <URL redacted by Avo>. If you diff these against tracking-plan property names, reconcile naming conventions first or you will report false discrepancies.
Redaction can also map two distinct names onto the same placeholder, so propertyNameSignature is not guaranteed to be free of duplicates. In the CSV those duplicates collapse into a single column and the last type wins.
The window here is a fixed 24 hours, and the most recent hour is not visible at all — see the time windows are fixed above.
CSV output
?format=csv returns the same rows shaped for diffing: causing shapes in one section, healthy shapes in another, with one column per property name so the two halves line up column for column. Reach for it when you want to eyeball a shape difference or hand the result to a spreadsheet rather than parse it.
The response is text/csv; charset=utf-8, lines joined with \n, no trailing newline and no BOM. The structure is fixed:
- Line 0 is always the truncation marker, emitted for both verdicts:
# variationsTruncated: trueor# variationsTruncated: false. # Variations causing the issue, then a header line, then the causing rows.# Variations not causing the issue, then the same header line again, then the remaining rows.
Both section headers are emitted even when a section is empty, and both sections repeat an identical header line so the two halves diff column for column. Causing rows come first.
Columns, in order:
event_variation_key, causing_issue, count, event_name, source_id, app_version,
min_created_at, max_created_at…followed by one column per property name: the union of propertyNameSignature across all rows, deduped in first-appearance order, with the causing rows scanned first. causing_issue is an explicit column rendered true / false.
Each property cell holds the type of that property in that row, and an empty cell when the row does not carry the property. A cell can also hold the literal unknown, which means the row supplied the name but no type at that position — a defensive fallback that the current ingestion path should never produce, but worth handling if you parse strictly. Date cells fall back to an empty cell rather than throwing on an invalid timestamp.
Quoting: every cell — including the header line — is wrapped in double quotes, except an empty string, which stays bare. Internal " is doubled. A cell starting with =, +, -, @, tab, CR or LF is prefixed with ' as a CSV injection guard.
# variationsTruncated: false
# Variations causing the issue
"event_variation_key","causing_issue","count","event_name","source_id","app_version","min_created_at","max_created_at","currency","payment_method","revenue"
"5d2b81f0a37c94e618df05b2c7a3e9410fb86d24c503a1e79b0d4f6238ca7e15","true","1428","Checkout Completed","9Zq7YAo0R","8.14.2","2026-08-23T07:00:00.000Z","2026-08-24T06:00:00.000Z","string","string","string"
"b0f47ac125d3e896402fc7b13a5d90e648127cf3ab05d9e7261340bfc85a92d6","true","96","Checkout Completed","9Zq7YAo0R","8.13.1","2026-08-23T07:00:00.000Z","2026-08-24T05:00:00.000Z","string",,"string"
# Variations not causing the issue
"event_variation_key","causing_issue","count","event_name","source_id","app_version","min_created_at","max_created_at","currency","payment_method","revenue"
"e93c4a70b1d582f6047ae3c9128d5b0f76a2e841c30f9b57d6812ac4053e7fb9","false","94776","Checkout Completed","9Zq7YAo0R","8.14.2","2026-08-23T07:00:00.000Z","2026-08-24T06:00:00.000Z","string","string","float"In that example the second causing row has no payment_method property, so its cell is bare.
Status codes
| Code | Body | Condition |
|---|---|---|
200 | JSON or CSV | Success. |
400 | {"error": "Invalid request"} | A non-string route parameter — for example a duplicated :issueId. Checked before authentication. |
401 | {"message": "Authorization header missing"}, {"message": "Authorization header missing or invalid"} or {"message": "Invalid authorization"} | Missing or invalid credential — see authentication error bodies for which is which. |
403 | {"message": "Access denied to workspace"} | Valid Bearer credential, not a member of :workspaceId. |
404 | {"error": "Issue not found"} | The id is not in this workspace’s rows. Note the lowercase not found, unlike the single-issue endpoint’s Issue Not found. |
500 | {"error": "Internal Server Error"} | Identity error, row-fetch error, connection-pool failure, or an uncaught throw. |
This endpoint fails closed on the causing-key lookup: if that lookup errors it returns 500 rather than a 200 with every row marked non-causing.
Example
Request
$ curl -H "authorization: Basic <Base64 encoded token>" \
-X GET "https://api.avo.app/workspaces/hAtPI0dEsq/inspector/issues/2f1c9b8e4d7a05c3e6b1a94f8d2c70b5e93a17d4c8f0b62a5d1e7c3948fb0a26/variations?sourceId=9Zq7YAo0R&appVersion=8.14.2"Response
{
"variations": [
{
"eventVariationKey": "5d2b81f0a37c94e618df05b2c7a3e9410fb86d24c503a1e79b0d4f6238ca7e15",
"causingIssue": true,
"count": 1428,
"eventName": "Checkout Completed",
"sourceId": "9Zq7YAo0R",
"schemaId": "hAtPI0dEsq",
"appVersion": "8.14.2",
"minCreatedAt": "2026-08-23T07:00:00.000Z",
"maxCreatedAt": "2026-08-24T06:00:00.000Z",
"eventKey": "a4e1c07b93d5f28601ab7c4e9d0f3b2586c1a97e4f0b3d8c25e6a1470bf9d3c8",
"sourceKey": "hAtPI0dEsq-9Zq7YAo0R",
"propertyNameSignature": ["currency", "payment_method", "revenue"],
"propertyTypeSignature": ["string", "string", "string"]
},
{
"eventVariationKey": "e93c4a70b1d582f6047ae3c9128d5b0f76a2e841c30f9b57d6812ac4053e7fb9",
"causingIssue": false,
"count": 94776,
"eventName": "Checkout Completed",
"sourceId": "9Zq7YAo0R",
"schemaId": "hAtPI0dEsq",
"appVersion": "8.14.2",
"minCreatedAt": "2026-08-23T07:00:00.000Z",
"maxCreatedAt": "2026-08-24T06:00:00.000Z",
"eventKey": "a4e1c07b93d5f28601ab7c4e9d0f3b2586c1a97e4f0b3d8c25e6a1470bf9d3c8",
"sourceKey": "hAtPI0dEsq-9Zq7YAo0R",
"propertyNameSignature": ["currency", "payment_method", "revenue"],
"propertyTypeSignature": ["string", "string", "float"]
}
],
"variationsTruncated": false
}The two shapes carry the same property names and differ only in the type of revenue — string on the shape causing the issue, float on the healthy one. That diff, plus the count ratio, is what these endpoints are for. Note that eventKey and sourceKey are identical on both rows: they identify the observed event name and the source, not the shape.
What’s next?
Now that you can read issues over HTTP, the conceptual docs explain what you are looking at and what to do about it:
- Issue types in Inspector — what each
issueTypedetects, in the same language the Avo web app uses. - Inspector issues view — the same issues in the Avo web app, including issue statuses and regressions.
- Fixing issues found in Inspector — turning a variation diff into a tracking plan or implementation change.
- Authentication — creating a service account and building the
Authorizationheader.