AT AlphaTest Reports

Reports Integrator API · v1

Turn a scored attempt into an explainable mastery report.

Pass Platform3 IDs through unchanged. After the declared upstream gates open, AlphaTest will compose live QTI, Results, Content, CASE, OneRoster, Analytics, Caliper, and mastery-engine facts—so AcmeTest can explain a score, show the exact gaps, and consume test-improvement receipts without calculating or storing learning facts.

  • Native IDsNo translation layer
  • Evidence in every claimCASE + QTI receipts
  • No shadow storePlatform3 stays authoritative

Built for the AcmeTest maintainer

One afternoon, no scoring code.

Use the QTI attempt ID you already have, ask for a bounded report view, render the returned state and evidence, and branch on stable problem types. The API never asks your app to join Platform3 products or infer mastery.

Contract preview — production report routes are not released today. The public synthetic contract sandbox is callable now for attempt-client wiring, but it contains no tenant data and makes no Platform3 calls. Attempt detail and readiness require Gates C+D; candidate history also requires an authoritative candidate/attempt index; cohort and equivalence require Gates A+D; materialization requires Gates B+C+D; improvement analysis, alignment receipts, and revision orders require Gates F+D. Gate E parks implementation and every later build stage until a signed prerequisite manifest dereferences the applicable receipts. The production runtime does not advertise closed routes. After release, an unavailable required dependency returns typed 503 dependency-unavailable, never empty or synthetic success. See all six gates · Read the active build-eligibility policy.

Cold integration guide

Make a successful call now; keep production gated.

The sandbox lets you prove response parsing and UI wiring against exact contract fixtures. It is intentionally not a substitute for production auth, tenant isolation, writes, or upstream-call evidence.

CALLABLE NOW · SYNTHETIC

1. Fetch an evidence report

Use the fixed sandbox attempt att_7F3k9. The Authorization header is accepted only so your request code has the production shape; the synthetic sandbox does not validate or inspect it.

export REPORTS_SANDBOX_URL="https://alphatest-andymontgomery-9773s-projects.vercel.app/reports/integrator_api/customer_website/sandbox"
export REPORTS_TOKEN="${REPORTS_TOKEN:-sandbox-not-validated}"

curl --fail-with-body --get \
  "$REPORTS_SANDBOX_URL/v1/attempt-reports/att_7F3k9" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "view=evidence" \
  --output report.json

node -e 'const r=require("./report.json"); if(r.score.value!==87||r.mastery[0].caseGuid!=="case_tx_5_3a"||r.mastery[0].evidence[0].qtiItemId!=="item_301") process.exit(1); console.log(`${r.gate.status}: ${r.mastery[0].state} ${r.mastery[0].caseGuid}`)'

Expected: fail: gap case_tx_5_3a. The response also carries QTI item/outcome evidence, a Results component id, and source receipts. Compare the normative fixture · Evidence decision

1b. Parse an adaptive placement report

The second fixed attempt proves the adaptive-only branch: a mastery-scale score, uncertainty, calibration and scale versions, stopping reason, administered item count, and evidence-backed mastered/not-yet/gap classifications.

curl --fail-with-body --get \
  "$REPORTS_SANDBOX_URL/v1/attempt-reports/att_diag_8K2m" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "view=evidence" \
  --output diagnostic.json

node -e 'const r=require("./diagnostic.json"); const gap=r.mastery.find(x=>x.caseGuid==="case_math5_alg_patterns"); if(r.testKind!=="adaptive_diagnostic"||r.gate!==null||r.diagnostic.scaleScore!==214.6||r.diagnostic.scaleVersion!=="ams-math-2"||r.diagnostic.calibrationVersion!=="cal-2026-01"||r.diagnostic.standardError!==2.7||r.diagnostic.stoppingReason!=="classifications_complete"||r.diagnostic.administeredItemCount!==24||gap?.state!=="gap"||!gap.evidence[0].qtiItemId) process.exit(1); console.log(`${r.diagnostic.scaleScore} ± ${r.diagnostic.standardError}: ${gap.state} ${gap.caseGuid}`)'

Expected: 214.6 ± 2.7: gap case_math5_alg_patterns. gate is null because it does not apply; nweaNormsSet is null because this fixture claims no MAP crosswalk. Compare the normative fixture · Diagnostic decision

CLIENT WIRING CHECK

2. Exercise materialize, list, readiness, and equivalence

These fixtures make the exact write request, pagination, and two audit projections callable without implying that Results, Caliper, Analytics, or QTI were contacted. Materialize always returns the same fixed synthetic operation and does not inspect the key or body. Candidate history enforces testKind against its fixture rows: an unmatched kind returns an empty terminal page.

curl --fail-with-body \
  "$REPORTS_SANDBOX_URL/v1/attempt-reports:materialize" \
  -X POST \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acmetest-att-7F3k9-v1" \
  --data '{"attemptId":"att_7F3k9","administrationId":"admin_fall_2026","view":"summary"}'

curl --fail-with-body --get \
  "$REPORTS_SANDBOX_URL/v1/candidates/student_1042/attempt-reports" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  --data-urlencode "testKind=mastery_gate" \
  --data-urlencode "limit=2" \
  --output attempts-page-1.json

NEXT_CURSOR=$(node -p 'require("./attempts-page-1.json").nextCursor')
curl --fail-with-body --get \
  "$REPORTS_SANDBOX_URL/v1/candidates/student_1042/attempt-reports" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  --data-urlencode "testKind=mastery_gate" \
  --data-urlencode "limit=2" \
  --data-urlencode "cursor=$NEXT_CURSOR" \
  --output attempts-page-2.json
node -e 'const p=require("./attempts-page-2.json"); if(p.nextCursor!==null||p.hasMore!==false) process.exit(1); console.log(`pagination complete: ${p.data.length} final row`)'

curl --fail-with-body \
  "$REPORTS_SANDBOX_URL/v1/attempt-reports/att_7F3k9/readiness" \
  -H "Authorization: Bearer $REPORTS_TOKEN"

curl --fail-with-body \
  "$REPORTS_SANDBOX_URL/v1/forms/qti_form_math5_b/equivalence" \
  -H "Authorization: Bearer $REPORTS_TOKEN"

Expected list result: pagination complete: 1 final row. The readiness response is not_ready because the same attempt's gate failed at 0.71 < 0.80. The sandbox only resolves the request shapes listed on its fixture manifest. It does not implement real writes, replay semantics, arbitrary IDs, auth failures, or dependency failures.

Production handoff

Exact post-gate request sequence

Keep these calls disabled until the release matrix names a production API origin and Gate E's signed manifest proves the build was eligible. Unlike the sandbox, these routes must verify the tenant JWT and compose real Platform3 facts.

  1. 1

    Set the API origin and JWT

    Use a signed tenant-scoped Platform JWT. Reads require reports:read; materialization requires reports:write. Tenant routing comes only from tenantId in the verified token.

    export REPORTS_BASE_URL="https://YOUR_REPORTS_API_ORIGIN"
    export REPORTS_TOKEN="YOUR_SIGNED_PLATFORM_JWT"

    The origin placeholder is deliberate: no production API origin is published while the routes are unreleased. Required claims and authorization behavior · Decision ITD-021

  2. 2

    Add the administration ID, then prepare materialization

    After Gates B, C, and D open together, this retry-safe orchestration writes settled components to Results, emits Caliper, reads Results back, and composes the report. The approved body requires both the QTI attempt ID and its AlphaTest administration ID; retain the latter from your assignment flow. Keep the same key for an identical retry. Do not call the path before it is released.

    curl --fail-with-body \
      "$REPORTS_BASE_URL/v1/attempt-reports:materialize" \
      -X POST \
      -H "Authorization: Bearer $REPORTS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json" \
      -H "Idempotency-Key: acmetest-att-7F3k9-v1" \
      --data '{"attemptId":"qti-attempt-01J0EXAMPLE","administrationId":"admin_fall_2026","view":"summary"}'
    Gates B, C, and D are closed. The route is not released, so no production success or Reports problem response is promised today. Once released, a required upstream outage returns the typed 503 fixture in the reference. The service must never guess a Results mapping, bypass two-tenant proof, or write locally. Contract fixtures · Reopening evidence.
  3. 3

    Implement the post-Gates-C+D evidence read

    After Gates C and D open in one coherent release, view=evidence returns identity, settled score, test-kind detail, standards/KCs, contributing QTI outcomes, source freshness, and explicit omission metadata. Before release, the path has no production contract.

    curl --fail-with-body --get \
      "$REPORTS_BASE_URL/v1/attempt-reports/qti-attempt-01J0EXAMPLE" \
      -H "Authorization: Bearer $REPORTS_TOKEN" \
      -H "Accept: application/json" \
      --data-urlencode "view=evidence"

    Views: summary, standards, evidence · Exact response fixture · AttemptReport fields

  4. 4

    Implement candidate attempt discovery

    After Gate C, Gate D, and the authoritative candidate/attempt index open together, candidate lists use stable, tenant-bound cursor pagination. Every row must satisfy every supplied server-enforced filter after authorization, including qtiTestId, testKind, status, and modifiedSince; an unmatched filter returns 200 with an empty terminal page. The alias testId is intentionally unsupported and returns the typed unsupported-parameter problem. Send the returned cursor with the same filters, sort, and limit; never decode it. Before release, the path has no production contract.

    curl --fail-with-body --get \
      "$REPORTS_BASE_URL/v1/candidates/student_1042/attempt-reports" \
      -H "Authorization: Bearer $REPORTS_TOKEN" \
      --data-urlencode "testKind=mastery_gate" \
      --data-urlencode "modifiedSince=2026-07-01T00:00:00Z" \
      --data-urlencode "limit=25"

    Response fixture · Filters, sort, and cursor validation · Server-enforced filter invariants

  5. 5

    Queue the post-administration improvement run

    After Gates F and D open, send the scored administration ID with a stable idempotency key and no request body. The run composes owner-produced Analytics/Caliper metrics, one structural-alignment record per named source test, and deterministic Bank or Blueprint revision orders. Reports does not recompute item statistics or mutate either owner.

    curl --fail-with-body \
      "$REPORTS_BASE_URL/v1/administrations/admin_fall_2026/improvement-runs:analyze" \
      -X POST \
      -H "Authorization: Bearer $REPORTS_TOKEN" \
      -H "Accept: application/json" \
      -H "Idempotency-Key: acmetest-admin-fall-2026-analysis-v4" \
      --output improvement-run.json
    
    RUN_ID=$(node -p 'require("./improvement-run.json").improvementRunId')
    curl --fail-with-body \
      "$REPORTS_BASE_URL/v1/improvement-runs/$RUN_ID" \
      -H "Authorization: Bearer $REPORTS_TOKEN"
    Gates F and D are closed. Keep this call disabled until authenticated owner artifacts, same-tenant Bank/Blueprint receiver commands, two-tenant isolation, and the complete acceptance chain are proven. A synthetic fixture is available in the field reference for parser construction; it is not a callable production claim.

Your client is gate-ready when…

It keeps calls disabled until the release matrix names a production origin; after release, it uses returned scores and gate decisions without recomputing them, renders every evidence-backed gap, exposes source freshness, follows cursors unchanged, treats unavailable improvement metrics as explicit omissions, verifies receipt digests, and branches on problem type—never message text.

Post-gate recipes

One schema, three test kinds

These normative recipes become populated only after Gates C and D open. The authoritative attempt-to-Results-to-Content composition determines test kind; callers do not select it. One unified runtime returns only the evidence that kind can support.

mastery_gate

Certify, or show the complete gap list

Render the Results-owned pass/fail under the immutable blueprint criteria. A failure includes every gap; missing criteria or coverage evidence is indeterminate, never pass.

report.gate.statusreport.gate.gapCaseGuids[]report.mastery[].evidence[]

GateDecision fields · ITD-006

adaptive_diagnostic

Place with a scale score and named gaps

Show the RIT-like mastery scale score beside its uncertainty, calibration version, stopping reason, and mastered/not-yet/gap map. Any MAP comparison is a labeled crosswalk—not an NWEA-issued score.

report.diagnostic.scaleScorereport.diagnostic.standardErrorreport.mastery[].state

DiagnosticDetail fields · ITD-007

formative

Return immediate per-standard evidence

Use the same endpoint for weekly unit tests and quizzes. Formative reports do not claim grade-level certification or external readiness unless their blueprint carries qualifying coverage evidence.

report.testKind === "formative"report.mastery[]report.sources[]

MasteryRow fields · ITD-008

Readiness · Gates C+D

“Will this student pass STAAR?”—with receipts

After Gates C and D open together, only complete coverage at equal-or-higher DOK plus satisfied gate criteria can produce projected_pass. Rule gate-implies-readiness version 2 means the attempt's gate must be pass, coverageFraction must equal 1, dokDominance must be satisfied, and qualifying blueprint slots must be nonempty. A failed gate produces not_ready; missing or stale evidence produces indeterminate. Before release, the route is absent.

curl --fail-with-body \
  "$REPORTS_BASE_URL/v1/attempt-reports/qti-attempt-01J0EXAMPLE/readiness" \
  -H "Authorization: Bearer $REPORTS_TOKEN"

The endpoint is reserved in architecture; the approved field contract is the embedded ReadinessReport projection. Readiness decision

Parallel forms · Gates A+D

Prove the retake form is equivalent

After Gates A and D open together, inspect coverage/DOK match, sample sizes, method, confidence interval, tolerance, decision, analysis version, and the downloadable audit artifact. Before release, the route is absent. insufficient_evidence remains first-class post-gate.

curl --fail-with-body \
  "$REPORTS_BASE_URL/v1/forms/qti_form_math5_b/equivalence" \
  -H "Authorization: Bearer $REPORTS_TOKEN"

The endpoint is reserved in architecture; the dictionary publishes the minimum post-gate EquivalenceReport obligation, while Analytics still must supply the owning wire contract before release. Equivalence decision

Post-administration improvement · Gates F+D

Turn every administration into auditable revision work.

One run connects owner-produced item/form analytics to a complete per-source alignment ledger, then hands deterministic actions to Bank or Blueprint. Every number and handoff keeps its owning artifact and version.

1 · Observe

Compose metrics; never invent private math

The run carries exactly one status for item difficulty, discrimination, misconception-tagged distractor distribution, and form summary. Values, populations, windows, suppression rules, and formulas pass through authenticated Analytics/Caliper artifacts. A missing independent metric is unavailable, not zero.

MetricStatus fields · ITD-036

2 · Prove

Quantify each named source without hiding gaps

alignmentPercent = 100 × Σ(weight × coverage × rigor) / Σ(weight). Missing, ambiguous, stale, uncovered, or lower-DOK mappings stay in the contribution ledger and add zero; an empty denominator is invalid. Predicted-versus-actual readiness remains a separate Analytics-owned comparison.

AlignmentReport fields · ITD-037

3 · Act

Deliver thresholded orders to the owner

Released, versioned policies can emit weak-item regeneration orders to Bank or calibration-delta orders to Blueprint. Each order identifies the reason, observed metric, threshold, evidence, requested action, deterministic ID, and target receipt. Missing receiver capability becomes blocked_upstream; Reports never edits QTI, banks, or blueprints directly.

RevisionOrder fields · ITD-038

Alignment receipt

Download the proof behind the score

curl --fail-with-body \
  "$REPORTS_BASE_URL/v1/improvement-runs/$RUN_ID/alignment-reports/staar-math-5" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  --output alignment.json

node -e 'const a=require("./alignment.json"); if(a.contributions.length<1||a.denominator<=0) process.exit(1); console.log(`${a.sourceAssessment}: ${a.alignmentPercent}%`)'

The complete contribution ledger prevents silent denominator shrinkage. Contribution rules

Improvement ledger

Page every receiver handoff

curl --fail-with-body --get \
  "$REPORTS_BASE_URL/v1/improvement-runs/$RUN_ID/revision-orders" \
  -H "Authorization: Bearer $REPORTS_TOKEN" \
  --data-urlencode "targetModule=bank" \
  --data-urlencode "limit=25"

Follow nextCursor unchanged. Completion requires every tripped order to have an acknowledgement or explicit blocked-upstream state and every evidence link to dereference. Paging contract · Acceptance rule

What Reports stores: tenant-scoped run, order, delivery, idempotency, and receipt-pointer workflow state only. It stores no student identity, response, score, mastery fact, distractor count, aggregate, or copied owner artifact. Storage boundary · ITD-039.

Endpoint reference

The complete intended v1 surface

Once a route is released, successes use application/json and failures use RFC 9457 application/problem+json.

Need exact fields, enums, nullability, filters, or errors?

The bundled data dictionary gives every request and response field a stable anchor, owning system, validation rule, and architecture citation.

Open field reference

Build safely

Auth, tenancy, retries, and errors

The server fails closed at every tenant-data boundary.

JWT claims

Required: sub, tenantId, iat, exp, role, and permission scope. Resource claims can narrow student, class, and school access. Production exposes no token-mint endpoint; short-lived reviewer credentials are minted out of band by the deployment pipeline.

Production credential boundary

Non-enumerating 404

A missing resource and one outside the token’s claims return the same safe detail. Do not treat 404 as proof that an upstream entity does not exist.

Authorization decision

Safe replay after release

Materialize and improvement-analysis commands require idempotency. An identical retry within 24 hours suppresses duplicate work and returns Idempotent-Replayed: true; the same key with a different request returns 409. Both commands remain undeployed behind their gates.

Replay contract

Typed problems

After release, branch on absolute type, HTTP status, and retryable. Correlate support with requestId and traceId.

All stable problem types
Recovery example409 · not retryable unchanged
{
  "type": "https://alphatest-andymontgomery-9773s-projects.vercel.app/reports/integrator_api/data_dictionary/problems/idempotency-conflict",
  "title": "Idempotency key conflicts with an earlier request",
  "status": 409,
  "detail": "Use a new Idempotency-Key for a different request body.",
  "instance": "/problems/occurrences/req_Z2d6",
  "requestId": "req_Z2d6",
  "traceId": "trace_M9p1",
  "retryable": false,
  "dependency": null,
  "fieldErrors": null,
  "operationId": "op_93wK"
}

Gap declarations

Unavailable upstreams stay visible

A local substitute that looks correct would violate the product contract. Closed gates keep routes unreleased; after release, transient dependency failures use typed problems.

Gate A

Analytics rollup and equivalence contracts unavailable

The class, school-grade, and form-equivalence routes are reserved but not deployed. Platform3 must publish and verify a production Analytics URL and versioned schema first. Reports never computes aggregates or equivalence privately.

Gate A decision
Gate B

Results components + Caliper protocol unverified

The client-facing materialize request is specified, but the upstream Results test_result_components write/read schema and retry-safe Caliper protocol are not. Materialize remains undeployed; no guessed mapping, local outbox of learning facts, or component table is allowed.

Gate B protocol
Gate C

Exact lookup exists; the complete evidence join does not

Platform3 confirms authenticated ?qtiAttemptId= lookup, but its published response still lacks the dereferenceable candidate, administration, QTI session/test/item outcomes, Content test spec, test kind, gate, mastery/KC, and Results components required for a report. Attempt detail, candidate lists, readiness, and materialization therefore remain undeployed—never partial reports or producer-id guesses.

ITD-028 and reopening proof
Gate D

Coherent runtime and two-tenant proof unavailable

No capability releases until one immutable unified runtime passes the three-kind matrix and two production-signed, disjoint tenant fixtures prove same-tenant success, bidirectional non-enumerating denial, anonymous rejection, and cache/idempotency/trace isolation. An unavailable credential or fixture keeps certification inconclusive and the routes closed.

ITD-031 release proof
Gate E

Implementation is parked until receipts exist

Specification work can continue, but implementation, surface QC, and integration cannot start until one signed prerequisite manifest dereferences every applicable immutable A/B/C/D receipt. Adaptive diagnostics additionally require production-learner QTI member/KC evidence, acknowledged Results components, and Caliper read-back. Stubs, sandbox payloads, public token mints, and permanent 503s do not open this gate.

ITD-041 active build eligibility
Gate F

Improvement-loop owner contracts unavailable

Improvement routes remain undeployed until authenticated Analytics/Caliper artifacts expose the exact item/form grain, Content supplies immutable crosswalk inputs, and Bank plus Blueprint accept tenant-safe idempotent receiver commands. Missing metrics stay unavailable; missing receivers stay blocked upstream. Reports never substitutes local analytics or silently completes an undelivered order.

Gate F owner requirements

Platform3 remains the record

AlphaTest composes; it does not copy.

Attempts and sessions stay in QTI. Outcomes and mastery stay in Results. Rollups and item/form metrics stay in Analytics/Caliper. Blueprints stay in Content, standards in CASE, rosters in OneRoster, and revision targets in Bank or Blueprint. Every response names the source resource, observed time, version, and freshness.

QTIResultsContentCASEAnalyticsBank / BlueprintReports
live composition