API platform

Adaptive sessions and readiness advice, over REST

The Partner API at /api/v1 lets your backend create learners, run full computer-adaptive sessions, and read structured readiness advice. Partner learners run through the exact same question bank, adaptive engine, and scoring as the consumer product.

The basics

Built like an API you'd want to consume

Bearer-key authentication

Every endpoint (except GET /health) authenticates with Authorization: Bearer pdk_…. Keys are minted in the partner portal, scoped per integration (six scopes: learners, sessions, advice, cat, topics, usage), and the full secret is shown exactly once — only a SHA-256 hash is stored. Optional expiries and one-click rotation cover staff churn and suspected exposure.

Structured JSON errors

Errors are never bare strings or stack traces. Every failure returns { error: { code, message, … } } with a stable machine-readable code (invalid_key, scope_missing, rate_limited, limit_exceeded, validation_error, …) plus extra fields where useful. Branch on the code, not the HTTP status.

Rate limits

10 requests/second sustained per API key, with bursts up to 20 (token bucket). A 429 with code rate_limited tells you exactly how long to back off via retry_after_seconds.

Plan limits, transparently metered

Plans set monthly ceilings per metric (API calls, adaptive sessions, advice calls, widget loads) plus a lifetime learner cap. GET /usage returns live usage against every limit and raises warnings at 80% — the same numbers the portal shows. Exhausted limits fail with limit_exceeded naming the metric, usage, and limit.

Error shape — example
{
  "error": {
    "code": "scope_missing",
    "message": "This key lacks the 'advice' scope.",
    "required_scope": "advice",
    "key_scopes": ["learners", "sessions"]
  }
}

Integration loop

The whole integration in four calls

Create a learner under your own ID, start an adaptive session, submit answers, read advice. Grading is server-side — the correct key and explanation appear only after an answer is submitted, so question payloads are structurally answer-free.

curl — learner → session → answer → advice
# 1. Create (or upsert) a learner — idempotent by external_id
curl -X POST https://passdeed.com/api/v1/learners \
  -H "Authorization: Bearer pdk_your_secret_here" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "student-1042", "name": "Dana Whitfield", "email": "dana@example.com"}'

# 2. Start an adaptive diagnostic session (returns the first question)
curl -X POST https://passdeed.com/api/v1/sessions \
  -H "Authorization: Bearer pdk_your_secret_here" \
  -H "Content-Type: application/json" \
  -d '{"external_learner_id": "student-1042", "mode": "diagnostic"}'

# 3. Submit an answer (repeat until "finished": true; each response carries next_question)
curl -X POST https://passdeed.com/api/v1/sessions/SESSION_ID/responses \
  -H "Authorization: Bearer pdk_your_secret_here" \
  -H "Content-Type: application/json" \
  -d '{"question_id": "QUESTION_ID", "selected_key": "A", "response_ms": 21500}'

# 4. Full structured advice (metered as advice_calls)
curl -X POST https://passdeed.com/api/v1/advice \
  -H "Authorization: Bearer pdk_your_secret_here" \
  -H "Content-Type: application/json" \
  -d '{"external_learner_id": "student-1042"}'

Two recovery endpoints round out the loop: POST /cat/next-question re-fetches the current outstanding question after a crash or page reload without advancing the session, and POST /sessions/{id}/complete seals the ability estimate when a learner walks away mid-session (idempotent).

The payoff

Structured advice your code can act on

POST /v1/advice returns the full learner model: readiness with confidence, weak and unseen topics, recommended actions whose params feed straight back into POST /sessions, an ordered study plan, and per-concept review notes.

StructuredAdvice — abbreviated
{
  "learner_external_id": "student-1042",
  "generated_at": "2026-06-12T16:02:11.000Z",
  "summary_text": "Readiness 58/100 (On track), 95% confidence 42–74, …",
  "readiness_score": 58,
  "readiness_band": "On track",
  "confidence": {
    "standard_error": 0.41,
    "interval_low": 42,
    "interval_high": 74,
    "items_answered": 35,
    "sessions_completed": 2
  },
  "weak_topics": [
    { "domain_id": "tx-contracts", "name": "Promulgated Contracts & Forms",
      "score": 38, "evidence_n": 6, "low_confidence": false }
  ],
  "strong_topics": [ … ],
  "unseen_topics": [ { "domain_id": "tx-special", "name": "Special Topics …" } ],
  "recommended_actions": [
    { "type": "focus_session",
      "title": "Focused practice: Promulgated Contracts & Forms",
      "reason": "Weakest measured domain (38/100 from 6 questions).",
      "params": { "mode": "practice", "length": 20, "domain_ids": ["tx-contracts"] } }
  ],
  "study_plan": [ { "order": 1, "title": "Rebuild Promulgated Contracts & Forms", … } ],
  "review_notes": [ { "concept": "option-period-deadlines", "missed_count": 2, … } ],
  "warnings": ["coverage_gap: 1 exam domains not yet measured"]
}

Honesty is part of the contract: a learner with no completed sessions gets a valid payload with readiness_score: null and a no_data warning — and estimates built on thin or stale evidence carry low_evidence / stale_estimate / coverage_gap warnings instead of overclaiming. Readiness is always an estimate with a 95% confidence range, never a promised outcome.

The widget

A server-authorized widget in one script tag

Your backend mints a five-minute token for one learner and one allowed origin; the script renders that learner's readiness card under your tenant branding. The live sample below never touches tenant data.

Live embed of /widget/frame — demo data is illustrative, not a real learner.

Embed snippet
<script src="https://YOUR-PASSDEED-HOST/widget/exam-advisor.js"
        data-widget-token="SHORT_LIVED_SERVER_ISSUED_TOKEN"
        data-theme="auto"></script>
  • Your server calls POST /api/v1/widget-sessions with a secret pdk_ key. Only the short-lived, learner-bound token reaches the browser.
  • The token is valid only for its signed tenant, learner, exact origin, read operation, and expiration. The script renders the returned HTML in a sandboxed iframe.
  • The signed token already contains the tenant and learner; the browser cannot substitute either id. Themes: light / dark / auto.

The full reference ships with onboarding

This page is the overview. The complete endpoint-by-endpoint reference (API_DOCS.md) and the step-by-step tenant integration guide ship with your partner onboarding, alongside your pilot tenant and keys.

PassDeed is not affiliated with or endorsed by TREC, Pearson VUE, or any state regulatory body. Passing standards are set by TREC. Verify current requirements at trec.texas.gov.