Authentication
Publishable (pk_) and secret (sk_) keys, origin allow-listing, rotation, and revocation.
On this page
Proctor has two API credentials, like Stripe:
- App keys (
pk_…) — public, candidate-browser credentials for the SDK. - Secret keys (
sk_…) — private, backend-only credentials for the server API.
(Operator sign-in to the dashboard is a separate session-based flow and needs no key.)
App keys (pk_)
App keys are public — they ship in your candidate's browser as SDK config. The full value is the key (pk_live_ followed by 21 characters); it stays visible in the dashboard at all times, so there is nothing to lose or vault. Each key is tied to your organization, and the server tags every incoming event with that organization. Treat them as scoped public identifiers, not private server secrets.
Secret keys (sk_)
Each app key can carry one server-to-server secret. In the dashboard → Settings → Keys, click Issue API secret on a key. The full sk_live_… value is shown exactly once — only a SHA-256 hash is stored, so copy it straight into your backend's secrets (env var / secrets manager). Re-issuing rotates it: the old secret stops working immediately.
Use it as a Bearer token against the /v1 server API:
Authorization: Bearer sk_live_…The server compares it timing-safe against the stored hash and scopes every request to your organization. The secret authorizes reading and changing your data, so it must never reach the browser — a candidate who had it could reset their own exam.
Origin allow-list
Each app key has a per-origin allow-list checked on every public SDK request:
- Empty list — no origin check; any origin may use the key.
- Exact origins — only listed origins are accepted; anything else is rejected with
403. *.example.com— wildcard-subdomain entries match any subdomain of that domain. The recommended middle ground when your exam pages span many subdomains.*— allow all origins. Local demos only.
# In Settings -> Keys -> edit
allowedOrigins: ["https://exam.acme-uni.edu", "*.exam.acme-uni.edu"]The allow-list also gates recording uploads
Recording chunks upload directly from the candidate's browser to the storage bucket, and the server keeps the bucket's CORS rule in sync with the union of every active app key's allowedOrigins (re-reconciled whenever a key is created, edited, rotated, or revoked). Entries — including wildcard ones — are carried into the bucket rule as-is.
This changes the calculus for the lax options:
- An empty allow-list contributes nothing to the bucket rule. The origin check is skipped, but your exam pages' origins never reach the bucket CORS — recordings from them fail unrecoverably, and post-session analysis shows
failed. Empty is only safe when your policy records nothing. - A literal
*entry is written into the bucket rule, allowing uploads from any origin — it works, but opens the bucket's upload CORS to the world.
So: list your real origins (or a *.domain wildcard) on every key whose sessions record, even where the API-side origin check wouldn't force you to.
Rotating and revoking
Revocation is immediate — it is checked per request, so the moment you revoke, the next request using that app key is rejected.
Two ways to rotate an app key:
- One click:
POST /app-keys/:id/rotate(or Rotate in the dashboard). Atomic — revokes the old key and creates a replacement in one step, carrying the name and origin allow-list forward. You only have to swap thepk_value in your integration. - Manual: create a replacement key, switch your test runner to the new value, confirm events land, then revoke the old key. Use this when you want an overlap window.
Rotate a secret key by clicking Issue API secret again — the old sk_ stops working the moment the new one is issued.
For high-stakes exams, rotate keys on a schedule (every quarter is a good default). The manual flow gives you a rollout window; the one-click rotate is the fast path when the old key may be compromised.