Skip to main content

Embedding and links

Getting the candidate into the assessment.

Two ways in. Pick one.

A link you send

curl -X POST https://api.experthire.cloud/v1/assessments/$ASSESSMENT_ID/launch-link \
  -H "Authorization: Bearer $EH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expires_in_seconds": 604800}'

Still a placeholder: $ASSESSMENT_ID, $EH_SECRET_KEY. Add them under Your values above.

201Response
{
  "object": "launch_link",
  "url": "https://careers.example.com/launch/ehs_lt_9Qm2...",
  "assessment_id": "3b8e1d02-5c77-4c2a-8a41-9b2f7e6d4c10",
  "expires_at": 1767830400,
  "livemode": true
}

Single use, seven days by default and seven at most. The URL resolves to the customer's own branded domain when one is configured, so the candidate never sees ours.

The token is hashed at rest, so a database read cannot mint a session from it. Opening it twice returns launch_token_used.

Embedded in your page

Three steps, and the secret key stays on your server throughout.

1. Allowlist your origin, once.

curl -X POST https://api.experthire.cloud/v1/domains \
  -H "Authorization: Bearer $EH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"origin": "https://app.example.com"}'

Still a placeholder: $EH_SECRET_KEY. Add it under Your values above.

Exact match on scheme, host and port. No wildcards.

This is not CORS. Adding an origin here gates session minting and framing; it will never add an Access-Control-Allow-Origin header to any response.

2. Mint a session on your server, scoped to one assessment, and hand the token to the page.

curl -X POST https://api.experthire.cloud/v1/sessions \
  -H "Authorization: Bearer $EH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assessment_id": "$ASSESSMENT_ID", "origin": "https://app.example.com"}'

Still a placeholder: $EH_SECRET_KEY, $ASSESSMENT_ID. Add them under Your values above.

201Response
{
  "object": "session",
  "session_token": "eyJhbGciOiJFZERTQSIsImtpZCI6...",
  "jti": "c4a1f9e2-8b73-4d16-9f05-2e7c3a8d6b41",
  "expires_at": 1767226500,
  "refresh_after": 1767226320,
  "assessment_id": "3b8e1d02-5c77-4c2a-8a41-9b2f7e6d4c10",
  "environment": "live",
  "livemode": true
}

3. Run the room. The page uses the session token, never the secret key. These four are the whole runtime.

CallPurpose
GET /v1/sessionWhat to render, including the capability flags below
POST /v1/assessments/{id}/joinRealtime credentials; starts the interview
GET /v1/assessments/{id}/timerServer-authoritative remaining time
POST /v1/assessments/{id}/endClose the room

join is idempotent. Rejoining after a refresh or a dropped connection returns fresh realtime credentials without restarting the clock or dispatching a second interviewer.

4. Refresh before it expires. The response carries refresh_after; call POST /v1/sessions/refresh with the session token before that. A mid-interview 401 is the most common embedding bug.

Code editor and whiteboard

GET /v1/session and the join grant both return supports_code_editor and supports_whiteboard. Render the matching surface when they are true and post the work back as the candidate goes:

CallPurpose
POST /v1/assessments/{id}/codeSave the editor contents
POST /v1/assessments/{id}/whiteboardSave the whiteboard

These flags come from the round type, not from the assessment. A coding round sets supports_code_editor and a system_design round sets supports_whiteboard; the default general_interview sets neither. If you create an assessment without passing round_type, you get general_interview and no editor. This is the most common reason the editor appears to be missing, and it is not related to sandbox.

Save as you go rather than only at the end. The interviewer scores whatever was last posted, so unsaved work at the final moment is not scored.

Origin is checked every request

The session token travels in a URL fragment, so binding the origin once at mint would let a stolen token replay from anywhere. It is re-checked on every call.

Revoking

DELETE /v1/sessions/{jti} ends a session immediately. The row behind the token is what makes that real; the JWT alone would stay valid until it expired.

Next

  • Reports — read the scored result
  • Errors — what to branch on