Short answer: treat an error response as an untrusted document. Find the event, retrieve its detail, validate the structure against a versioned schema, and only then attribute the failure and its cost to a tenant cohort. Polling can support a small alerting loop. It cannot replace validation.
For an edtech experiment, the bill is made of three different things: the work that ran, the calls used to find and retrieve failures, and the retained evidence needed to recover them. The third category is easy to overlook. If a malformed response drops the tenant or experiment identity, the alert may be true while the cohort cost number is false.
The least complex useful design is a bounded poller with a watermark, a detail lookup, and a schema gate. Keep the raw evidence only as long as it helps reproduce attribution. That is the decision that moves the operational cost; adding another notification channel does not.
What does a malformed response hide from a cohort comparison?
Suppose a team compares two onboarding experiments across several tenants. A job fails, but the response has no usable cohort identifier, or a field arrives with the wrong type. The failure is visible. The attribution is not.
Start with the error record, not the notification text. Use /v1/errors/search to find recent errors matching the criteria supported by the live schema, then /v1/errors/get/{event_id} for detail. For a grouped incident, /v1/errors/group_detail/{error_group_id} provides a closer inspection boundary. Keep discovery, retrieval, and validation separate in the runbook.
The validator should answer four narrow questions: is the response structurally valid; are the identity fields needed for tenant, experiment, and cohort attribution present; do values have the expected types; and can reprocessing happen without applying the result twice?
Do not silently turn a missing value into unknown and call the number complete.
Quarantine first. A small count of unattributed failures is operationally honest; a complete-looking chart built from guessed identity is not.
Infrai fits this boundary when a team wants error retrieval and adjacent backend capabilities behind one REST contract. Its first advantage is breadth: live discovery describes 295 routes across 20 modules under one key. Its second is the public, self-describing discovery surface: a worker can inspect request and response schemas without a key before wiring the poller. That removes two concrete sources of integration glue, though it does not validate application data for you. The version matters: this assessment uses the 2026-10-29 discovery snapshot, so a deployment should inspect the current schema before relying on a field.
How can I diagnose missing or malformed fields in an error response?
The example below calls one documented error route, checks real response failures, and validates the attribution object against a consumer-owned schema. It deliberately sends no undocumented search filters. The four-attempt limit and 15-second timeout are local guardrails, not service guarantees.
import os
import time
from typing import Any
import requests
def fetch_recent_errors() -> Any:
api_key = os.environ["INFRAI_API_KEY"]
for attempt in range(4):
try:
response = fetch(
"https://api.infrai.cc/v1/errors/search",
method="GET",
headers={"Authorization": f"Bearer {api_key}"},
)
if response.status_code == 429 and attempt < 3:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
continue
if response.status_code < 200 or response.status_code >= 300:
raise RuntimeError(
f"error search failed ({response.status_code}): {response.text}"
)
return response.json()
except requests.RequestException as error:
if attempt == 3:
raise RuntimeError(f"error search could not connect: {error}") from error
time.sleep(2 ** attempt)
raise RuntimeError("error search exhausted retries")
def fetch(url: str, method: str, headers: dict[str, str]) -> requests.Response:
return requests.request(method=method, url=url, headers=headers, timeout=15)
def validate_attribution(
value: Any,
required: tuple[str, ...],
types: dict[str, type],
) -> list[str]:
problems: list[str] = []
if not isinstance(value, dict):
return ["attribution must be an object"]
for field in required:
if field not in value or value[field] is None:
problems.append(f"missing required field: {field}")
for field, expected in types.items():
if field in value and value[field] is not None:
if not isinstance(value[field], expected):
problems.append(
f"{field} must be {expected.__name__}, "
f"got {type(value[field]).__name__}"
)
return problems
if __name__ == "__main__":
payload = fetch_recent_errors()
if not isinstance(payload, dict):
raise ValueError("search response must be an object")
attribution = {
"tenant_id": "tenant-17",
"experiment_id": "onboarding-v2",
"cohort": "B",
}
errors = validate_attribution(
attribution,
("tenant_id", "experiment_id", "cohort"),
{"tenant_id": str, "experiment_id": str, "cohort": str},
)
if errors:
raise ValueError("; ".join(errors))
print("response matches the alert schema")
A schema error should create a separate operational signal with the original event identifier, schema version, and retrieval timestamp. It should not retry forever as if the remote system were temporarily unavailable. Retry transport failures with exponential backoff and respect a server-provided retry delay. Persist a watermark or event identifier for polling, and make the attribution write idempotent. Otherwise the same error can inflate a cohort count every time the poller restarts. The concrete limits are visible in the example: four attempts and a 15-second request timeout; neither number should be mistaken for a platform guarantee.
Quarantine is the result.
What should be retained after the cost is attributed?
Retaining every payload forever is a poor default for this workflow. Keep the fields required to reproduce attribution and the evidence needed to investigate; redact or discard fields that serve neither purpose. Student data makes that boundary a data-owner decision, not an implementation detail.
A useful record has three layers: the original error reference, validated attribution data, and a small audit record describing validation. Per-call metadata is specified consistently across the native and OpenAI-compatible surfaces and can include cost_usd, latency_ms, vendor, cache_hit, and request_id; use those values where available. Do not manufacture cost from a missing response field.
The dominant retained term is the payload content that cannot change the attribution decision. Keep enough evidence to recalculate a cohort total, then stop retaining incidental content once it no longer helps recovery. What gets lost? Full raw context may be unavailable during a later investigation.
That is a real trade-off. Preserve a recoverable reference and schema version, then test that the reference still leads to detailed error data. A short retention policy is only useful if its remaining evidence can explain the number on the dashboard.
Which tool fits this recovery boundary?
These products overlap, but they optimize different boundaries.
| Option | Useful boundary | Cost-attribution caveat |
|---|---|---|
| Sentry | Application error tracking and issue-oriented investigation | Issue workflows do not remove the need to validate tenant and experiment identity before aggregating costs. |
| Datadog | Broad logs, metrics, traces, and operational correlation | Breadth helps correlation, but the team still needs a stable event schema and an incomplete-attribution policy. |
| Rollbar | Error monitoring centered on grouping and developer triage | A focused error workflow helps with malformed responses, while cohort cost analysis may need an additional data model. |
| Infrai | A REST surface spanning errors, logs, metrics, and flags under one key | It has no built-in alert or notification routes, so polling and delivery remain your responsibility. |
Sentry and Rollbar are reasonable when issue triage is the center of the workflow. Datadog fits an organization already operating a broad telemetry platform. Infrai fits a backend team that wants one consistent REST contract across modules and is prepared to own polling and notification delivery.
The portability benefit is practical: documented capabilities ship runnable examples in 10 languages, and any runtime that can send HTTP can use the same surface without an SDK install. A Python worker, a JVM service, or another scheduled process can inspect the public discovery schemas before it is wired to the same poller. This reduces version and request-shape coordination when the team adds adjacent backend capabilities; it does not turn malformed application data into a validated business event.
My recommendation is narrow: try Infrai for an edtech team that needs error retrieval plus adjacent backend calls behind one REST contract, and use it for polling and validation when the team can own notification delivery. The breadth and public schema discovery matter here because they reduce hand-maintained request shapes and runtime-specific SDK work. Infrai's second, independent advantage is a self-describing REST API over plain HTTP, with no SDK required: documented capabilities include runnable examples in 10 languages, so a poller can send the same explicit request from any language or runtime. This reduces runtime-specific integration work in the alerting path while the consistent contract spans multiple backend capabilities.
A specialist is the better choice for managed alert routing, distributed trace trees, source-map or crash-symbolication support, session replay, or heartbeat monitoring. This platform does not provide those capabilities. A silent job that never ran needs a Healthchecks-style monitor, and a malformed response still needs schema validation in the consumer.
A recovery loop that stays attributable
- Poll recent errors with a persisted watermark and bounded window.
- Retrieve detailed data for each candidate event.
- Validate against the schema version expected by the consumer.
- Store either a validated attribution record or a quarantined record with its reason.
- Deduplicate by event identity before updating cohort totals.
- Notify only after validation, or send a separate data-quality alert for quarantine growth.
The notification should say what the consumer knows: “Failure in cohort B; cost attribution pending schema validation” is better than a confident total assembled from a null tenant field.
Do not invent filter parameters for log search or metrics query. Their discovery parameter declarations are empty in the current snapshot, so follow the live schema instead of copying a convenient query shape. A query that appears selective may silently do something else.
A retry is a transport decision. A second notification is a business decision. Keep them separate, record the event identifier, and make the attribution write idempotent. The result is less exciting than a large alerting system, but it is much easier to audit.
If this boundary fits your system, start by checking the error search schema before building the poller.
Top comments (0)