{
  "module": "caliper",
  "surface": "1edtech",
  "deliverable": "customer_website",
  "generatedAt": "2026-05-28T16:01:20.832Z",
  "benchmark": {
    "url": "https://docs.stripe.com/api",
    "matchedQualities": [
      "top-level Authentication and Errors sections",
      "copy-paste base URL and cURL setup",
      "inline request and response tables per endpoint",
      "stable deep links and searchable index",
      "single-page API reference usable by an implementer without reading source code"
    ]
  },
  "approvedInputs": {
    "architectureUrl": "https://platform3-andymontgomery-9773s-projects.vercel.app/caliper/1edtech/architecture/",
    "dataDictionaryUrl": "https://platform3-andymontgomery-9773s-projects.vercel.app/caliper/1edtech/data_dictionary/",
    "architectureItddCount": 16,
    "dataDictionaryTables": 6,
    "dataDictionaryFields": 69,
    "allowedValueSets": 9,
    "allowedValues": 207
  },
  "liveApi": {
    "canonicalTarget": "https://platform3-andymontgomery-9773s-projects.vercel.app/caliper/1edtech/implementation/api",
    "baseExpression": "https://platform3-andymontgomery-9773s-projects.vercel.app/caliper/1edtech/implementation/api",
    "baseEnv": "CALIPER_BASE_URL",
    "reviewerTokenEnv": "CALIPER_REVIEWER_JWT",
    "demoTokenPath": "/dev/mint",
    "demoTenantId": "00000000-0000-4000-8000-00000000ca12",
    "demoSensorIri": "https://timeback.example.edu/sensors/caliper-demo",
    "demoEventIri": "urn:uuid:11111111-1111-4111-8111-111111111111"
  },
  "liveQuickstartVerification": {
    "verifiedAt": "2026-05-28T16:00:25Z",
    "baseUrl": "https://platform3-andymontgomery-9773s-projects.vercel.app/caliper/1edtech/implementation/api",
    "demoTenantId": "00000000-0000-4000-8000-00000000ca12",
    "result": "mint returned token/tenant/sensor; POST /caliper/v1p2/events returned 204 with caliper-envelope-hash; GET /events returned AssessmentItemEvent Completed; GET /envelopes returned processed."
  },
  "endpoints": [
    {
      "operationId": "mintDemoToken",
      "kind": "Demo helper",
      "method": "POST",
      "path": "/dev/mint?tenantId={tenantId}",
      "rootPath": "/api/dev/mint?tenantId={tenantId}",
      "statusCodes": [
        "200",
        "400",
        "403"
      ],
      "requestSchema": [
        {
          "name": "tenantId",
          "in": "Query",
          "type": "uuid",
          "required": "Required",
          "description": "Use the seeded demo platform tenant UUID 00000000-0000-4000-8000-00000000ca12. Malformed or missing values return 400; real-tenant tokens are not minted by this helper."
        },
        {
          "name": "Authorization",
          "in": "Header",
          "type": "none",
          "required": "Not allowed",
          "description": "No bearer token is required for public demo token minting. Real-tenant JWTs are minted by operators or by the loop driver."
        }
      ],
      "responseSchema": [
        {
          "name": "token",
          "type": "string",
          "required": "Required",
          "description": "HS256 bearer token signed with the platform secret and scoped to the demo tenant."
        },
        {
          "name": "tenantId",
          "type": "uuid",
          "required": "Required",
          "description": "The seeded demo tenant UUID 00000000-0000-4000-8000-00000000ca12."
        },
        {
          "name": "role",
          "type": "string",
          "required": "Required",
          "description": "Demo role, usually sensor_writer or reviewer."
        },
        {
          "name": "sensorIri",
          "type": "IRI",
          "required": "Required for quickstart",
          "description": "Seeded active sensor IRI accepted by POST /caliper/v1p2/events for the demo tenant."
        },
        {
          "name": "expiresAt",
          "type": "date-time",
          "required": "Required",
          "description": "Expiration time after which the token returns 401."
        }
      ],
      "traces": [
        "itd:citd-006-sensor-registration-tenant-auth"
      ]
    },
    {
      "operationId": "persistCaliperEnvelope",
      "kind": "Caliper Sensor API",
      "method": "POST",
      "path": "/caliper/v1p2/events",
      "rootPath": null,
      "statusCodes": [
        "204",
        "400",
        "401",
        "403",
        "409",
        "415",
        "422",
        "500"
      ],
      "requestSchema": [
        {
          "name": "Authorization",
          "in": "Header",
          "type": "Bearer JWT",
          "required": "Required",
          "description": "Signed, unexpired platform token. tenantId must match X-Timeback-Tenant."
        },
        {
          "name": "X-Timeback-Tenant",
          "in": "Header",
          "type": "tenant uuid",
          "required": "Required",
          "description": "Tenant boundary for the envelope, sensor lookup, and all normalized rows. Use the tenantId returned by demo minting or the tenantId claim from a real-tenant JWT."
        },
        {
          "name": "Content-Type",
          "in": "Header",
          "type": "application/json",
          "required": "Required",
          "description": "Only JSON Sensor API envelopes are accepted by this HTTP surface."
        },
        {
          "name": "Idempotency-Key",
          "in": "Header",
          "type": "string",
          "required": "Optional",
          "description": "Optional platform retry key. Duplicate Caliper delivery is still deduplicated by canonical envelope/event hashes."
        },
        {
          "name": "sensor",
          "in": "Body",
          "type": "IRI or Sensor entity",
          "required": "Required",
          "description": "Caliper envelope sensor. It must resolve to an active caliper.sensor row for the tenant."
        },
        {
          "name": "sendTime",
          "in": "Body",
          "type": "date-time",
          "required": "Required",
          "description": "Caliper envelope sendTime supplied by the sender."
        },
        {
          "name": "dataVersion",
          "in": "Body",
          "type": "IRI",
          "required": "Required",
          "description": "Must be http://purl.imsglobal.org/ctx/caliper/v1p2 for this surface."
        },
        {
          "name": "data[]",
          "in": "Body",
          "type": "array<Event|Entity>",
          "required": "Required",
          "description": "At least one Event or described Entity. Event rows require id, type, actor, action, object, and eventTime."
        }
      ],
      "responseSchema": [
        {
          "name": "body",
          "type": "empty",
          "required": "Always empty on success",
          "description": "204 No Content has no JSON body. Use the read projections for envelope/event evidence."
        },
        {
          "name": "raw_envelope",
          "type": "jsonb",
          "required": "Persisted, not returned",
          "description": "Original JSON-LD envelope is stored as the interchange authority."
        },
        {
          "name": "envelope_hash",
          "type": "sha256",
          "required": "Persisted, not returned",
          "description": "Canonical envelope hash used for duplicate detection and read projection lookup."
        }
      ],
      "traces": [
        "itd:citd-004-jsonld-envelope-authority",
        "itd:citd-007-envelope-first-ingest",
        "itd:citd-008-canonical-hashes-idempotency",
        "itd:citd-011-sensor-api-response-errors"
      ]
    },
    {
      "operationId": "getCaliperEventProjection",
      "kind": "TimeBack read projection",
      "method": "GET",
      "path": "/caliper/v1p2/events?eventIri={eventIri}",
      "rootPath": null,
      "statusCodes": [
        "200",
        "401",
        "403",
        "404",
        "500"
      ],
      "requestSchema": [
        {
          "name": "Authorization",
          "in": "Header",
          "type": "Bearer JWT",
          "required": "Required",
          "description": "Token tenantId must match X-Timeback-Tenant."
        },
        {
          "name": "X-Timeback-Tenant",
          "in": "Header",
          "type": "tenant id",
          "required": "Required",
          "description": "Tenant boundary for the event lookup. Must match the token tenantId claim."
        },
        {
          "name": "eventIri",
          "in": "Query",
          "type": "IRI",
          "required": "Required",
          "description": "Caliper Event id supplied in the original event object."
        }
      ],
      "responseSchema": [
        {
          "name": "eventRowId",
          "type": "uuid",
          "required": "Required",
          "description": "Platform row identifier for the normalized event."
        },
        {
          "name": "envelopeId",
          "type": "uuid",
          "required": "Required",
          "description": "Envelope row that carried the event."
        },
        {
          "name": "eventIri",
          "type": "IRI",
          "required": "Required",
          "description": "Caliper event id."
        },
        {
          "name": "eventType",
          "type": "enum",
          "required": "Required",
          "description": "Caliper Event subclass such as AssessmentItemEvent."
        },
        {
          "name": "profile",
          "type": "enum",
          "required": "Required",
          "description": "Caliper profile. If omitted by the sender, this projection may infer it from event type without changing raw_event."
        },
        {
          "name": "action",
          "type": "enum",
          "required": "Required",
          "description": "Caliper action term."
        },
        {
          "name": "eventTime",
          "type": "date-time",
          "required": "Required",
          "description": "Learning activity timestamp."
        },
        {
          "name": "rawEvent",
          "type": "json",
          "required": "Required",
          "description": "Original Caliper JSON-LD event. Returned only to the authenticated tenant."
        },
        {
          "name": "eventHash",
          "type": "sha256",
          "required": "Required",
          "description": "Canonical event hash used for replay evidence."
        }
      ],
      "traces": [
        "itd:citd-009-event-entity-relational-projection",
        "itd:citd-012-operational-read-projections"
      ]
    },
    {
      "operationId": "getCaliperEnvelopeProjection",
      "kind": "TimeBack read projection",
      "method": "GET",
      "path": "/caliper/v1p2/envelopes?hash={envelopeHash}",
      "rootPath": null,
      "statusCodes": [
        "200",
        "401",
        "403",
        "404",
        "500"
      ],
      "requestSchema": [
        {
          "name": "Authorization",
          "in": "Header",
          "type": "Bearer JWT",
          "required": "Required",
          "description": "Token tenantId must match X-Timeback-Tenant."
        },
        {
          "name": "X-Timeback-Tenant",
          "in": "Header",
          "type": "tenant id",
          "required": "Required",
          "description": "Tenant boundary for envelope lookup. Must match the token tenantId claim."
        },
        {
          "name": "hash",
          "in": "Query",
          "type": "sha256",
          "required": "Required",
          "description": "Canonical envelope hash."
        }
      ],
      "responseSchema": [
        {
          "name": "envelopeId",
          "type": "uuid",
          "required": "Required",
          "description": "Platform identifier for the received envelope."
        },
        {
          "name": "sensorIri",
          "type": "IRI",
          "required": "Required",
          "description": "Caliper envelope sensor IRI."
        },
        {
          "name": "dataVersion",
          "type": "IRI",
          "required": "Required",
          "description": "Accepted Caliper context/data version."
        },
        {
          "name": "sendTime",
          "type": "date-time",
          "required": "Required",
          "description": "Sender supplied envelope sendTime."
        },
        {
          "name": "receivedAt",
          "type": "date-time",
          "required": "Required",
          "description": "Platform receipt timestamp."
        },
        {
          "name": "envelopeStatus",
          "type": "enum",
          "required": "Required",
          "description": "received, processed, rejected, or duplicate."
        },
        {
          "name": "events[]",
          "type": "array",
          "required": "Required",
          "description": "Event summaries normalized from this envelope."
        }
      ],
      "traces": [
        "itd:citd-007-envelope-first-ingest",
        "itd:citd-008-canonical-hashes-idempotency",
        "itd:citd-012-operational-read-projections"
      ]
    }
  ],
  "statusCodes": [
    {
      "code": "200",
      "name": "OK",
      "applies": [
        "getCaliperEventProjection",
        "getCaliperEnvelopeProjection",
        "mintDemoToken"
      ],
      "meaning": "The read or demo helper request succeeded and returned JSON."
    },
    {
      "code": "204",
      "name": "No Content",
      "applies": [
        "persistCaliperEnvelope"
      ],
      "meaning": "The Sensor API envelope was accepted or safely deduplicated; no JSON body is returned."
    },
    {
      "code": "400",
      "name": "Bad Request",
      "applies": [
        "persistCaliperEnvelope",
        "mintDemoToken"
      ],
      "meaning": "Malformed JSON, missing required envelope fields, empty data array, invalid tenantId format, or invalid query syntax."
    },
    {
      "code": "401",
      "name": "Unauthorized",
      "applies": [
        "persistCaliperEnvelope",
        "getCaliperEventProjection",
        "getCaliperEnvelopeProjection"
      ],
      "meaning": "Missing, expired, malformed, or wrongly signed bearer token."
    },
    {
      "code": "403",
      "name": "Forbidden",
      "applies": [
        "persistCaliperEnvelope",
        "getCaliperEventProjection",
        "getCaliperEnvelopeProjection",
        "mintDemoToken"
      ],
      "meaning": "The token is valid but not authorized for the X-Timeback-Tenant value or the envelope sensor, or /dev/mint was called for a tenant that the public demo helper is not allowed to mint."
    },
    {
      "code": "404",
      "name": "Not Found",
      "applies": [
        "getCaliperEventProjection",
        "getCaliperEnvelopeProjection"
      ],
      "meaning": "No event or envelope exists for this tenant and identifier."
    },
    {
      "code": "409",
      "name": "Conflict",
      "applies": [
        "persistCaliperEnvelope"
      ],
      "meaning": "The optional Idempotency-Key was reused with a different payload, method, path, tenant, module, or surface."
    },
    {
      "code": "415",
      "name": "Unsupported Media Type",
      "applies": [
        "persistCaliperEnvelope"
      ],
      "meaning": "Content-Type is not application/json."
    },
    {
      "code": "422",
      "name": "Unprocessable Content",
      "applies": [
        "persistCaliperEnvelope"
      ],
      "meaning": "The JSON shape parses, but dataVersion, event type, action, entity type, timestamp, profile, extension, or privacy validation fails."
    },
    {
      "code": "500",
      "name": "Server Error",
      "applies": [
        "persistCaliperEnvelope",
        "getCaliperEventProjection",
        "getCaliperEnvelopeProjection"
      ],
      "meaning": "Unexpected platform failure. Problem JSON stays redacted and never includes learner payloads or tokens."
    }
  ],
  "validationRules": [
    {
      "name": "Envelope shape",
      "rule": "sensor, sendTime, dataVersion, and data are required; data must be an array with at least one Event or Entity.",
      "itd": "citd-007-envelope-first-ingest"
    },
    {
      "name": "dataVersion",
      "rule": "Only `http://purl.imsglobal.org/ctx/caliper/v1p2` is accepted for this surface.",
      "itd": "citd-005-vocabulary-validation-profile-inference"
    },
    {
      "name": "Event required properties",
      "rule": "Event id, type, actor, action, object, and eventTime are required before an item can become a normalized event.",
      "itd": "citd-005-vocabulary-validation-profile-inference"
    },
    {
      "name": "Controlled vocabulary",
      "rule": "Event type, action, entity type, and explicit profile must come from generated Caliper 1.2 term indexes.",
      "itd": "citd-001-source-authority-term-index"
    },
    {
      "name": "Profile inference",
      "rule": "Omitted profile may be derived from event type for normalized projection only; raw_event remains unchanged.",
      "itd": "citd-005-vocabulary-validation-profile-inference"
    },
    {
      "name": "Tenant scope",
      "rule": "JWT tenant_id or tenantId must match X-Timeback-Tenant before persistence or read projection.",
      "itd": "citd-006-sensor-registration-tenant-auth"
    },
    {
      "name": "Sensor status",
      "rule": "Only active sensors may ingest; paused, retired, unknown, and cross-tenant sensors are rejected.",
      "itd": "citd-006-sensor-registration-tenant-auth"
    },
    {
      "name": "Duplicate delivery",
      "rule": "Same tenant and same canonical envelope/event hash is a safe duplicate, not a second event row.",
      "itd": "citd-008-canonical-hashes-idempotency"
    },
    {
      "name": "Extension handling",
      "rule": "Unknown Caliper object properties are preserved as extensions but cannot satisfy required Caliper fields or redefine official terms.",
      "itd": "citd-010-extension-preservation"
    },
    {
      "name": "Privacy",
      "rule": "Problem details, audit metadata, logs, traces, search indexes, and conformance evidence must not echo raw learner PII or tokens.",
      "itd": "citd-013-privacy-redaction-learning-events"
    }
  ],
  "privacyRules": {
    "raw_payloads": "Raw Caliper payloads are tenant-scoped sensitive records. They may preserve valid Caliper PII but are not copied to logs, Problems, audit metadata, public docs, search indexes, or conformance evidence.",
    "read_projection": "Read projections require tenant-matched JWTs and return only the caller's tenant data.",
    "examples": "Public examples use sample data and must not resemble real student, parent, teacher, token, or tenant identifiers."
  },
  "sourceFilesRead": [
    "https://docs.stripe.com/api",
    "loop/caliper/artifacts/1edtech/architecture/site/caliper-architecture-traceability.json",
    "loop/caliper/artifacts/1edtech/architecture/site/index.html",
    "loop/caliper/artifacts/1edtech/data_dictionary/source/caliper-data-dictionary-source.json",
    "loop/caliper/artifacts/1edtech/data_dictionary/site/index.html",
    "vendor/caliper-prior-workspace/spec_bundle/MANIFEST.md",
    "vendor/caliper-prior-workspace/ARCHITECTURE.md",
    "vendor/caliper-prior-workspace/docs/1edtech-caliper-package.md",
    "vendor/caliper-prior-workspace/docs/plain-english-guide.md",
    "vendor/caliper-prior-workspace/docs/caliper-relational-json-architecture.md",
    "vendor/caliper-prior-workspace/docs/conformance-and-documentation.md",
    "vendor/caliper-prior-workspace/docs/adr/0001-generated-term-index.md",
    "vendor/caliper-prior-workspace/docs/adr/0002-postgresql-primary.md",
    "vendor/caliper-prior-workspace/docs/adr/0003-jsonld-authority.md",
    "vendor/caliper-prior-workspace/docs/adr/0004-canonical-hashes.md",
    "vendor/caliper-prior-workspace/docs/adr/0005-data-dictionary-provenance.md",
    "vendor/caliper-prior-workspace/docs/adr/0006-tenant-sensor-boundary.md",
    "vendor/caliper-prior-workspace/docs/adr/0007-envelope-first-ingest.md",
    "vendor/caliper-prior-workspace/docs/adr/0008-entity-event-projection.md",
    "vendor/caliper-prior-workspace/docs/adr/0009-extension-preservation.md",
    "vendor/caliper-prior-workspace/docs/adr/0010-conformance-evidence.md",
    "vendor/caliper-prior-workspace/docs/adr/0011-api-boundary-auth-hosting.md",
    "vendor/caliper-prior-workspace/migrations/001_caliper_core.sql",
    "vendor/caliper-prior-workspace/contracts/caliper-boundary.openapi.yaml",
    "vendor/caliper-prior-workspace/src/caliper/caliperModel.ts",
    "vendor/caliper-prior-workspace/src/caliper/caliperNormalizer.ts",
    "vendor/caliper-prior-workspace/src/repository/caliperRepository.ts",
    "vendor/caliper-prior-workspace/src/repository/postgresCaliperRepository.ts",
    "vendor/caliper-prior-workspace/tests/fixtures.ts",
    "vendor/caliper-prior-workspace/tests/caliperNormalizer.test.ts",
    "vendor/caliper-prior-workspace/tests/supabaseRepository.test.ts",
    "vendor/caliper-prior-workspace/site/index.html",
    "vendor/caliper-prior-workspace/site/search-index.json",
    "vendor/caliper-prior-workspace/site/spec-traceability.json",
    "loop/caliper/artifacts/1edtech/implementation/summary.json",
    "loop/caliper/artifacts/1edtech/implementation/impl/src/http/caliperHttp.mjs",
    "loop/caliper/artifacts/1edtech/implementation/impl/tests/deployed.smoke.test.mjs"
  ],
  "correctionsFromPriorWorkspace": [
    "The published contract uses one CALIPER_BASE_URL for demo and real tenants; the public demo path is /dev/mint?tenantId=00000000-0000-4000-8000-00000000ca12 on that same deployed API, matching the approved Caliper architecture endpoint and live implementation descriptor.",
    "Prior caliper.tenant table is not used; tenant scope inherits platform.tenant.",
    "Prior shared_secret_ref naming is replaced by credential_ref in the approved data dictionary.",
    "Prior envelope_status value superseded is replaced by duplicate.",
    "Prior 202-with-body Sensor API response is replaced by 204 No Content with separate TimeBack read projections."
  ]
}