Skip to content
Jetrepo
Esc
navigateopen⌘Jpreview
On this page

Start a preview

Return a single-use agent-created review URL without exposing a Delivery key or Preview Bearer.

Your frontend owns routing and rendering. A delegated agent creates a Candidate-bound Preview handoff, claims its URL once, and returns that URL to the reviewer. The reviewer needs no Jetrepo login: possession of the short-lived link is the review capability.

Agent workflow

Use the existing four Management tools; Preview adds no fifth tool.

  1. Inspect DELIVERY and select an enabled Verified Preview Target. The descriptor includes candidate_preview.create@1, the ten-minute handoff lifetime, and the control_operation:CLAIM_SECRET step.
  2. Propose a Change Set containing candidate_preview.create@1, bound to the stable Backend, Environment, exact Candidate ID and digest, target, and optional paired entity/type route context. Supply both route IDs or neither; Jetrepo rejects incomplete context before execution.
  3. Execute that handoff Candidate and wait for the Operation to reach SUCCEEDED.
  4. Call control_operation with action: "CLAIM_SECRET" for that Operation.
  5. Return the claimed URL to the reviewer. The claim is bound to the same principal and credential, disappears after the first claim, and is never retained in Candidate, Operation, item outcome, or audit data.

The Operation result contains only the Preview session ID, expiry, immutable Candidate provenance, and secretClaimAvailable: true.

{
  "stableBackendId": "BACKEND_ID",
  "environmentId": "ENVIRONMENT_ID",
  "candidateId": "CANDIDATE_ID",
  "candidateDigest": "64_CHARACTER_SHA256",
  "entityId": "OPTIONAL_PAIRED_ENTITY_ID",
  "typeId": "OPTIONAL_PAIRED_TYPE_ID",
  "targetId": "VERIFIED_TARGET_ID"
}

The claimed value is a link on the Jetrepo Management origin:

https://api.example.com/preview/handoffs/jp_boot_REDACTED

Opening the link does not consume it. Jetrepo presents an explicit confirmation so chat and email link scanners cannot spend the capability. Confirmation server-posts the one-time bootstrap code to the verified frontend route. The frontend exchanges it, installs an opaque browser session, and removes the Jetrepo handoff URL from the active page.

Publish the frontend protocol

Serve GET /.well-known/jetrepo-preview from the deployed frontend origin. @jetrepo/preview/protocol provides the strict descriptor and response helpers.

{
  "protocolVersion": 1,
  "origin": "https://preview.example.com",
  "bootstrap": {
    "paths": [
      "/preview/article/{entityId}",
      "/preview/landing_page/{entityId}"
    ],
    "method": "POST",
    "contentType": "application/x-www-form-urlencoded",
    "formField": "jetrepo_preview_code"
  },
  "readiness": {
    "sessionFormField": "jetrepo_preview_session",
    "messageType": "jetrepo-preview-status"
  },
  "capabilities": { "readOnly": true }
}

A delegated agent with previewTarget.update can register the target through preview_target.create@1. New and imported targets remain Unverified. After deployment, run preview_target.verify@1; Jetrepo checks public DNS, bounded redirects, response size and type, canonical origin, protocol version, and bootstrap-path agreement before marking the target Verified.

Changing the origin or URL template resets verification and revokes existing sessions. Failed verification stores a safe diagnostic code without retaining fetched content or network addresses.

Exchange the code

The frontend server receives jetrepo_preview_code as a form field and exchanges it immediately. Do not add an authentication header.

curl https://delivery.example.com/preview/sessions/exchange \
  -H 'content-type: application/json' \
  --data '{"bootstrapCode":"jp_boot_REPLACE_ME"}'
{
  "accessToken": "jp_prev_REPLACE_ME",
  "session": {
    "id": "SESSION_ID",
    "backendId": "PHYSICAL_BACKEND_ID",
    "environmentId": "ENVIRONMENT_ID",
    "candidateId": "CANDIDATE_ID",
    "candidateDigest": "64_CHARACTER_SHA256",
    "manifestHash": "64_CHARACTER_SHA256",
    "targetId": "TARGET_ID",
    "targetOrigin": "https://preview.example.com",
    "perspective": "CANDIDATE",
    "expiresAt": "2026-08-21T12:30:00.000Z"
  }
}

The handoff/bootstrap code is stored hashed, expires after ten minutes, and exchanges once. The returned Bearer expires after 30 minutes.

Fetch Candidate content

Keep the Bearer on your server and use it for Delivery REST:

curl https://delivery.example.com/rest/article/ENTRY_ID \
  -H 'authorization: Bearer jp_prev_REPLACE_ME'

Exchange and Preview responses are private and non-cacheable. Do not place them in a shared CDN cache. Clear the application session when Jetrepo reports expiry, revocation, target failure, Candidate drift, or an invalid Environment generation.

Share sessions across replicas

Use an opaque, HttpOnly, Secure, partitioned browser cookie. Store the associated Preview session in shared infrastructure so any frontend replica can serve the next request. @jetrepo/preview/redis provides a strict Redis-compatible store adapter with absolute expiry and malformed-state cleanup.

The process-local memory store is development-only and refuses production use. Do not fall back to it during a shared-store outage; fail closed instead.

Access and trust

Creating the handoff requires operation.execute and previewSession.create; claiming its URL requires operation.read and the exact principal plus credential that created the Operation. Jetrepo also rechecks operation.read, previewTarget.read, previewSession.create, and scoped content.read under the delegated authority before creating the session.

The receiving frontend needs no Management permission and the reviewer needs no Jetrepo account. The capability grants only read access to the exact immutable Candidate on the verified target. It cannot approve, execute, mutate, or publish.

Exchange and read errors

Condition Status
Malformed handoff URL 404
Expired, revoked, exchanged, or unavailable URL 410
Malformed exchange request 400
Invalid, replayed, expired, or revoked code 401
Disabled, failed, or unverified target 403
Invalid exchange context 403
Invalidated Candidate on the first content read 403
Candidate base drift on the first content read 409

Every subsequent REST read rechecks the exact Candidate digest, manifest materialization, base manifest, target, and authorization before returning content.

Was this page helpful?