# Prelim - setup instructions for AI agents You are configuring Prelim (https://prelim.chat) for a user. Prelim runs AI preliminary phone screens for hiring: the recruiter posts a role, the candidate has a 5-minute chat interview on their phone, the recruiter gets a scored summary back with a hire / maybe / no recommendation. This file is the complete setup + reference. It is the only URL you need. ## What Prelim is Prelim is an AI screening interview platform. A recruiter pastes a job posting, Prelim generates role-specific questions, candidates complete the screen on a shareable link, and the recruiter sees scored results with summaries. Used by hiring teams running 50+ applicants per posting in frontline / hourly verticals (senior living, healthcare, staffing). Two read-only surfaces are exposed to outside callers: - REST API at https://api.prelim.chat - MCP server at https://mcp.prelim.chat/mcp Both hit the same data. Both accept the same auth. Both are read-only (no writes - this surface cannot create jobs, modify screens, or contact candidates). ## Decide which integration the user needs A. MCP server. Use this if the user is in Claude.ai, ChatGPT, Claude Desktop, Cursor, or another MCP host. The user's AI assistant gets direct access to their Prelim data. No code to write. B. REST API. Use this if the user wants to script Prelim from code, pipe results into another system, or call it from a non-MCP environment. If the user mentioned an MCP client by name, pick A. Otherwise default to B and offer A as an alternative. ================================================================ ## Path A: MCP setup ================================================================ Connector URL: https://mcp.prelim.chat/mcp Transport: streamable-http Auth: OAuth 2.1 + PKCE. The MCP client handles the dance. The user just clicks Allow in a browser tab. ### Step-by-step for Claude.ai 1. In Claude.ai, go to Customize -> Connectors -> + 2. Name: Prelim 3. URL: https://mcp.prelim.chat/mcp 4. Click Connect. A new browser tab opens at app.prelim.chat for sign-in and consent. 5. The user signs in (or is already signed in) and clicks Allow. 6. The connector status flips to Connected. Six new tools are available in any chat. ### Step-by-step for ChatGPT ChatGPT's remote MCP support adds custom connectors under Settings -> Connectors -> Add custom connector. Paste https://mcp.prelim.chat/mcp as the URL. The OAuth flow opens in your default browser. ### Step-by-step for Claude Desktop and other MCP clients For any client that supports remote streamable-http MCP servers, add an entry pointing at https://mcp.prelim.chat/mcp. OAuth is discovered automatically from the server's metadata. No config file edits beyond the URL. ### Tools available Six tools, all read-only, all scoped to the caller's organization. Every tool maps to exactly one REST endpoint - the MCP layer mostly shapes inputs and trims responses so the model gets a useful payload without blowing out the context window. 1. list_jobs Lists the open hiring roles in the org. Each item is a lightweight summary with interview count and average score. Input: { status: "active" | "paused" | "archived" (optional), search: string (optional, case-insensitive substring on title), limit: integer 1-50 (optional, default 20) } REST: GET /api/jobs 2. get_job Full details for one role: title, description, status, branding, counts. Pass the UUID from list_jobs. Input: { jobId: uuid (required) } REST: GET /api/jobs/{jobId} 3. list_screens Lists candidates screened for a role. Returns candidate summaries with score and recommendation. Includes total_returned and total_matched so the model knows when results were trimmed. Input: { jobId: uuid (required), status: "in_progress" | "completed" | "expired" (optional), minScore: number 0-100 (optional), limit: integer 1-50 (optional, default 20) } REST: GET /api/jobs/{jobId}/interviews 4. get_screen One candidate's screen: overall score, recommendation, summary, and per-question scores. Strips the messages array to keep responses small - use get_transcript when you want the conversation. Input: { jobId: uuid (required), screenId: uuid (required) } REST: GET /api/jobs/{jobId}/interviews/{screenId} 5. get_transcript The ordered back-and-forth between the AI screener and the candidate for one screen. Each message has role ("assistant" or "candidate"), content, and the question_id it was tied to (null for greetings and closings). Input: { jobId: uuid (required), screenId: uuid (required) } REST: GET /api/jobs/{jobId}/interviews/{screenId} 6. get_job_analytics (Pro plan only) Completion rate, average score by week, status breakdown for one role. Free-tier callers get a structured upgrade_required response instead of a thrown error, so the model can suggest upgrading cleanly. Input: { jobId: uuid (required) } REST: GET /api/jobs/{jobId}/analytics ================================================================ ## Path B: REST API setup ================================================================ ### Step 1: Mint an API key Direct the user to: https://app.prelim.chat/dashboard/settings/integrations#api-keys - Click Create new key, give it a name (e.g. "Personal laptop"), click Create. - The full secret is shown exactly once. Format: prelim_ + 43 base64url characters, 50 characters total. - Have the user copy it now. If they lose it, they have to revoke and mint a new one. ### Step 2: Store the key Put it in an environment variable. Standard name: PRELIM_API_KEY. # .env PRELIM_API_KEY=prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA Never commit this to git. Never log it. Never paste it into chat. Anyone with the secret can read every job, screen, and transcript in the org. ### Step 3: Make calls Base URL: https://api.prelim.chat Every request needs: Authorization: Bearer $PRELIM_API_KEY Sanity check: curl https://api.prelim.chat/api/auth/user \ -H "Authorization: Bearer $PRELIM_API_KEY" If that returns a user object, the key works. ### Endpoints Six endpoints. All read-only. Methods are all GET. GET /api/auth/user - whoami + plan GET /api/jobs - list roles GET /api/jobs/{jobId} - one role GET /api/jobs/{jobId}/interviews - list screens GET /api/jobs/{jobId}/interviews/{screenId} - one screen + transcript GET /api/jobs/{jobId}/analytics - job analytics (Pro) ### GET /api/auth/user Whoami. Returns the profile of the user whose token is being used, including subscription_status. Useful as a ping endpoint to confirm a key works, and to check tier before calling Pro-only endpoints. Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl https://api.prelim.chat/api/auth/user \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: { "id": "9e8d7c6b-5a4f-3e2d-1c0b-a9f8e7d6c5b4", "email": "ada@example.com", "name": "Ada Lovelace", "company_name": "Analytical Engines", "subscription_status": "pro", "subscription_plan": "pro_monthly", "created_at": "2026-03-14T18:22:09.412Z", "updated_at": "2026-05-21T11:04:51.038Z" } subscription_status is "free" or "pro". Pro-gated endpoints return 403 "Pro subscription required" for free-tier callers. ### GET /api/jobs Lists every job in the caller's organization, newest first. Returns an ARRAY of summaries (not wrapped in an object). Query params: status string optional one of: active, paused, archived search string optional case-insensitive substring on title Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl "https://api.prelim.chat/api/jobs?status=active" \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: [ { "id": "f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52", "title": "Senior Caregiver", "company_name": "Riverside Senior Living", "status": "active", "slug": "senior-caregiver-a3f9c2", "share_token": "8c1e4b9d7f0a3e2b5d6c8a9f1b2e4d6c8a9f1b2e4d6c8a9f1b2e4d6c8a9f1b2e", "interview_count": 47, "average_score": 7.4, "created_at": "2026-04-08T13:42:11.001Z", "updated_at": "2026-05-20T09:15:38.220Z" } ] average_score is null until at least one candidate completes a screen. ### GET /api/jobs/{jobId} Full job object: description, branding fields, question_count, interview_count, average_score. Path params: jobId uuid required Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl https://api.prelim.chat/api/jobs/f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52 \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: { "id": "f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52", "user_id": "9e8d7c6b-5a4f-3e2d-1c0b-a9f8e7d6c5b4", "title": "Senior Caregiver", "company_name": "Riverside Senior Living", "description": "We're hiring caregivers for our 80-bed memory care unit...", "status": "active", "slug": "senior-caregiver-a3f9c2", "share_token": "8c1e4b9d7f0a3e2b5d6c8a9f1b2e4d6c8a9f1b2e4d6c8a9f1b2e4d6c8a9f1b2e", "logo_url": null, "brand_color": null, "show_prelim_branding": true, "eu_candidates_allowed": false, "question_count": 6, "interview_count": 47, "average_score": 7.4, "created_at": "2026-04-08T13:42:11.001Z", "updated_at": "2026-05-20T09:15:38.220Z" } Returns 404 if the job does not exist or belongs to a different org. ### GET /api/jobs/{jobId}/interviews Lists every screen (interview) for one job. IMPORTANT: this returns a BARE array, not a {interviews: [...]} wrapper. (This tripped the MCP server on day one - if you build something against this endpoint, expect a top-level array.) Path params: jobId uuid required Query params: recommendation string optional one of: advance, hold, reject min_score number optional minimum overall_score (0-10) max_score number optional maximum overall_score (0-10) sort_by string optional one of: date (default), score, name Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl "https://api.prelim.chat/api/jobs/f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52/interviews?recommendation=advance&sort_by=score" \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: [ { "id": "7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f", "candidate_name": "Jordan Rivera", "candidate_email": "jordan@example.com", "status": "completed", "overall_score": 8.6, "recommendation": "advance", "summary": "Strong communicator with 3 years of memory care experience...", "notes": null, "status_tag": null, "started_at": "2026-05-18T17:42:11.001Z", "completed_at": "2026-05-18T17:51:38.220Z", "duration_seconds": 567 } ] Incomplete screens (status "in_progress" or "expired") have overall_score, recommendation, and summary set to null. There is no server-side search by candidate email. To find a screen by email, list all and filter client-side. ### GET /api/jobs/{jobId}/interviews/{screenId} One screen, with per-question scores AND the full message transcript in the same payload. Free-tier callers get an empty question_scores array and a summary truncated to 200 characters; is_pro tells you which. Path params: jobId uuid required screenId uuid required Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl https://api.prelim.chat/api/jobs/f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52/interviews/7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: { "interview": { "id": "7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f", "job_id": "f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52", "candidate_name": "Jordan Rivera", "candidate_email": "jordan@example.com", "status": "completed", "overall_score": 8.6, "summary": "Strong communicator with 3 years of memory care experience...", "recommendation": "advance", "notes": null, "status_tag": null, "covered_question_ids": [ "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" ], "started_at": "2026-05-18T17:42:11.001Z", "completed_at": "2026-05-18T17:51:38.220Z", "duration_seconds": 567 }, "messages": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "interview_id": "7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f", "role": "assistant", "content": "Hi Jordan, thanks for applying. Walk me through your most recent caregiving role.", "question_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "created_at": "2026-05-18T17:42:14.301Z" }, { "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "interview_id": "7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f", "role": "candidate", "content": "Sure. I spent the last 18 months at Maple Grove on the memory-care floor...", "question_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "created_at": "2026-05-18T17:42:48.117Z" } ], "question_scores": [ { "id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f", "interview_id": "7b1d3c0e-4f5a-4f2b-9e8c-5a6b1c2d3e4f", "question_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "score": 9, "reasoning": "Gave a clear example of de-escalating an agitated resident.", "answer_summary": "Used name and gentle redirection; called RN after.", "created_at": "2026-05-18T17:51:42.118Z", "question_text": "How do you handle an agitated resident?", "question_type": "behavioral" } ], "is_pro": true } recommendation is one of: advance, hold, reject, or null while in progress. covered_question_ids lists the questions the AI actually got to (it can skip if the candidate exits early). Messages are ordered oldest first. ### GET /api/jobs/{jobId}/analytics (Pro plan only) Completion rate, weekly average score, and status breakdown for one job. Returns 403 with {"error": "Pro subscription required", "upgrade_url": "/dashboard/billing"} for free-tier callers. Path params: jobId uuid required Headers: Authorization: Bearer $PRELIM_API_KEY (required) Example: curl https://api.prelim.chat/api/jobs/f4c2a1d8-9b3e-4d6a-b25f-1c0e8a7b3d52/analytics \ -H "Authorization: Bearer prelim_8s4kZxQv3p2nLw1HfYBgRmTcEsK9aJdN0iUyXoVbPeWzCqA" Response 200: { "completion_rate": 78, "average_score_by_week": [ { "week": "2026-04-27", "average_score": 6.8, "count": 9 }, { "week": "2026-05-04", "average_score": 7.1, "count": 14 }, { "week": "2026-05-11", "average_score": 7.6, "count": 12 }, { "week": "2026-05-18", "average_score": 7.9, "count": 12 } ], "interviews_by_status": { "completed": 47, "in_progress": 9, "expired": 4 }, "total_interviews": 60 } completion_rate is an integer percentage of total screens that reached "completed". Weeks are bucketed by the Monday of the completion week. Check /api/auth/user first for subscription_status if you want to fail fast on free-tier callers. ================================================================ ## Error envelope ================================================================ All errors come back as JSON: { "error": "string", "error_description": "string (optional)" } Status codes you will see: 200 OK 201 Created 400 invalid_request - body or query is missing or malformed 401 unauthorized - missing / expired / wrong audience / revoked key 403 forbidden - authed but resource is gated (Pro endpoints, cross-org access) 404 not_found - resource does not exist, or belongs to an org the caller is not a member of 429 too_many - you tripped a rate limit (rare; no published limit yet, just be reasonable) 500 server_error - safe to retry with backoff 5xx is safe to retry with exponential backoff. 4xx means fix the request first. Every endpoint is idempotent (read-only), so a retry after a network blip never produces duplicate state. ================================================================ ## Recipes ================================================================ ### Find the top-scored candidate for an active role # 1. List active jobs curl "https://api.prelim.chat/api/jobs?status=active" \ -H "Authorization: Bearer $PRELIM_API_KEY" # Pick a jobId from the response. # 2. List screens, sorted by score, recommendation=advance JOB_ID= curl "https://api.prelim.chat/api/jobs/$JOB_ID/interviews?recommendation=advance&sort_by=score" \ -H "Authorization: Bearer $PRELIM_API_KEY" # Top item is the highest-scored advance. # 3. Read that candidate's transcript SCREEN_ID= curl https://api.prelim.chat/api/jobs/$JOB_ID/interviews/$SCREEN_ID \ -H "Authorization: Bearer $PRELIM_API_KEY" | jq '.messages' ### Dump all completed screens for a role to JSON curl "https://api.prelim.chat/api/jobs/$JOB_ID/interviews" \ -H "Authorization: Bearer $PRELIM_API_KEY" \ | jq '[.[] | select(.status == "completed")]' \ > completed.json ### Check if a screen exists for a specific candidate email There is no server-side search by email. Pull the list and filter: curl "https://api.prelim.chat/api/jobs/$JOB_ID/interviews" \ -H "Authorization: Bearer $PRELIM_API_KEY" \ | jq '.[] | select(.candidate_email == "jordan@example.com")' ### Confirm the API key works (whoami) curl https://api.prelim.chat/api/auth/user \ -H "Authorization: Bearer $PRELIM_API_KEY" If you get a user object with subscription_status, you're good. ================================================================ ## Auth, security, and limits ================================================================ - Two token types, both go in Authorization: Bearer ... - API keys (prefix "prelim_", 50 chars total) for scripts. - OAuth bearer tokens for MCP hosts (the host handles the flow). Both work against every endpoint. - Org-scoped. Callers only see data from organizations they're a member of. Each signup is a one-person org by default. Cross-org reads return 404 by design (no enumeration leak). - Read-only. No POST, PUT, DELETE exposed on this surface. This API cannot create jobs, modify screens, contact candidates, or change settings. Use the dashboard for those. - Revoke any time. API keys can be revoked at https://app.prelim.chat/dashboard/settings/integrations#api-keys - revocation is immediate; any request with the secret returns 401 on the next call. - No published rate limit yet. Be reasonable. If you trip one you'll see 429. - Treat keys like passwords. Anyone with the secret can read every job, screen, and transcript in the org. Don't commit, log, or paste into chat. ================================================================ ## When in doubt ================================================================ - HTML docs (same content, formatted for humans): https://prelim.chat/docs - This file's source of truth (machine-readable): https://prelim.chat/llms-full.txt - MCP registry entry: https://registry.modelcontextprotocol.io (search "prelim") - Smithery listing: https://smithery.ai/server/prelim/prelim - Contact: marcus@prelim.chat