MCP quickstart
Lovelio ships a Model Context Protocol server at https://lovelio.ai/api/mcp. Connect any MCP-aware client and run your recruiting desk with natural language. MCP is an open standard. Each client must still support Lovelio's HTTP transport, OAuth registration, HTTPS or loopback callbacks, and PKCE. Verify the actual client before a rollout.
One URL for every region
https://lovelio.ai/api/mcp works for every agency. When you sign in, Lovelio finds your workspace's region and keeps the connection there.
Each region also serves MCP on its own domain. An API key only works there:
https://us.lovelio.ai/api/mcp- Americas and rest of worldhttps://eu.lovelio.ai/api/mcp- UK, Ireland, and Europehttps://anz.lovelio.ai/api/mcp- Australia and New Zealand
Your region is the domain you sign in on, and both URLs are shown on the Settings -> API keys page.
Before you connect
Sign in as a Lovelio workspace admin. Your MCP client opens a consent page where you choose the permissions to grant. REST scripts use API keys; MCP uses OAuth for both reads and writes. Do not paste a REST key into an MCP Authorization header.
For a Connect sandbox, use Open sandbox in the portal first, then connect your MCP client to the sandbox origin shown there with /api/mcp.
Install
Start with the server URL in a compatible OAuth-enabled MCP client:
https://lovelio.ai/api/mcp
Clients that take a JSON config block want this. Most use the mcpServers key; a few name the wrapper differently:
{
"mcpServers": {
"lovelio": {
"type": "http",
"url": "https://lovelio.ai/api/mcp"
}
}
}
Clients with a settings screen ask for the same URL under a heading like Connectors, Custom connector, or Add MCP server. Clients with a command line have their own add command - pass the URL and the HTTP transport.
The first call opens the Lovelio sign-in and consent page. Approve the permissions you need. Dynamically registered clients are unreviewed; the consent page says so. Your client receives OAuth tokens and refreshes them automatically.
Compatible clients discover the OAuth endpoints through this metadata:
API-key connection when OAuth is incompatible
MCP also accepts an existing Lovelio API key in Authorization: Bearer <key>. Create a dedicated key in your region's Settings > API page. Select only the permissions the agent needs. Put the key in your client's secure header settings, not in a chat message or repository.
{
"mcpServers": {
"lovelio": {
"type": "http",
"url": "https://us.lovelio.ai/api/mcp",
"headers": { "Authorization": "Bearer <your-dedicated-Lovelio-key>" }
}
}
}
An API key works only on your workspace's region URL, never on https://lovelio.ai/api/mcp. An agency API key has agency-level record access; it does not impersonate a consultant. Its explicit scopes still filter tools and gate every V1 call. Key revocation and scope reductions apply on the next request. Unlike OAuth, a static key has no rotating refresh token or separate consent ceiling. Manage its scopes and revoke it in Settings > API. OAuth remains the route for a personal connection with the person's role, record access, agency rules and explicit consent.
This option supports clients that can send headers but cannot complete Lovelio's OAuth callback. It does not prove a particular client has securely stored the key or completed a workflow.
How the OAuth flow works
- Your client gets a 401 from the MCP endpoint and reads the discovery metadata above.
- It registers itself at
POST /api/oauth/register(dynamic client registration, public clients, PKCE S256 required) and gets aclient_id. - It opens
/api/oauth/authorizein a browser. A workspace admin signs in and approves selected permissions. - The client exchanges the code at
POST /api/oauth/tokenfor an access token (1 hour) and a refresh token (30 days). Lovelio stores token hashes and encrypts the internal API credential. The client receives OAuth tokens, limited to the approved scopes. - Every MCP call carries the access token. The client refreshes it in the background. Disconnecting revokes at
POST /api/oauth/revoke.
Complete recruiting workflows
The tool catalogue includes form templates, questionnaire drafts and completion, candidate import preparation and status, interview scheduling, candidate matching, record cleanup, and webhook maintenance. A visible tool still needs the agency grant and the caller's role permission. Personal OAuth actions use the connected person's current role and record access. Admins can grant the needed capabilities in Settings > Workspace > Agent access; role permissions remain in Settings > Workspace > Permissions. Existing connections must reconnect and consent to additional scopes.
For native phone references, use create_reference_instance, read get_form_instance, save actual answers with submit_form_instance and draft: true, then complete with draft: false. Use send_form_instance only for an intended email reference. Read the reference recipe for the full sequence.
For CV imports, call create_candidate_import, then add_candidate_import_files. Upload the actual file bytes to each returned signed upload_url; creating upload slots does not upload a CV. Call finalize_candidate_import with the uploaded item IDs and poll get_candidate_import. A processing acknowledgement does not mean parsing has completed.
Persist one idempotency_key per intended write. Reuse it for an identical retry. Read the resulting record to verify completion. Keep returned IDs so later calls address the same record.
A connected chatbot does not provide durable agency orchestration by itself. Use MCP for interactive consultant work. For unattended queues and webhooks, use a Connect integration with persisted jobs, event deduplication, retry receipts, and human approvals for hiring decisions.
Form reads respect team visibility. A form-list page can be empty while meta.has_more is true; continue using meta.next_cursor.
First tool call
Once connected, a test prompt:
List my 5 most recent jobs.
The agent calls list_jobs and returns a table.
Or an action:
Create a job for a Senior Backend Engineer, remote, GBP 110-140k base.
The agent drafts the job with create_job_from_description, asks which client the job is for, and confirms it with you before publishing.
What the tools can do
The tools call the V1 REST API and the in-app action registry. The generated catalogue and regression tests check that their routes and declared permissions agree. They do not guarantee every agency workflow is available: phone-reference creation and answer submission, form-template authoring and bulk CV import management currently require REST. The connection's scope ceiling and each action's permissions also apply. Human approval remains part of candidate decisions. The full REST surface is documented in the OpenAPI reference.
Both search_candidates and search accept page (starting at 1). Keep query and limit unchanged between pages. Candidate results contain data.results, total_count, page, and has_more; use the returned search notes and verification limits when explaining matches. A bare email address keeps its email filter without model interpretation. CV mentions are a separate tier, not confirmed employment matches. Every candidate returned by either search door counts against the agent access rules.
Toolsets
A connection lists the core toolset by default: about 35 tools for the everyday desk, small enough for any client's tool limit. Add ?toolsets= to the MCP URL for more, comma separated, or all for every tool:
https://lovelio.ai/api/mcp?toolsets=clients,analytics
https://lovelio.ai/api/mcp?toolsets=all
Toolsets only change what is listed. Scopes and permissions still decide what runs, and every write action runs through batch from core. Calling a tool outside the listed toolsets returns TOOL_NOT_IN_TOOLSET naming the toolset to add. The table and the tool list below show each tool's toolset.
Every tool below mirrors one V1 route or one registry action. Any write action without a typed tool still runs through batch, which takes write actions only; call list_actions or describe_action first for its payload shape and scope.
311 tools, 112 read and 199 write. Generated from the server's tool list; do not edit by hand.
| Toolset | Tools | What it adds |
|---|---|---|
core (default) | 37 | Everyday consultant loop: search (database and web), jobs, candidates, applications, notes, email, interviews, submissions, placements, plus batch for every other write. |
jobs | 27 | Job ads, job drafts, the describe-a-job flow, distribution, close and reopen. |
candidates | 32 | Candidate detail: tags, preferences, CVs, documents, duplicates, imports, evidence, adding people found on the web. |
pipeline | 27 | Application detail: bulk moves, offers, assessments, phone screens, email drafts, scheduled email, the review queue. |
interviews | 34 | Interviews, interview drafts, scorecards, calendar events, meetings and reminders. |
clients | 37 | Client contacts, sites, DNA, fee terms, specs, submission chasers, company intelligence, finding new clients, job leads, careers floats and key movements. |
talent_pools | 11 | Talent pools and their members. |
placements | 23 | Placement detail, contractors, check-ins, extensions, availability asks, documents for placements. |
forms | 23 | Forms, form instances, referees and reference checks. |
calls | 14 | Recorded calls, clips, uploads and transcription usage. |
analytics | 6 | Analytics dashboards and quotas. |
marketplace | 14 | Marketplace listings, intros and deals. |
admin | 26 | Account users and teams, webhooks, integrations, API keys, SSO, workflow rules, brand voice. |
Account and admin (accounts:read, accounts:write)
add_quota_adjustment(write, admin): Add a manual credit or debit to a consultant's (or the agency's) recognised billings for a period, with the reason.get_account_overview(read, admin): Get your Lovelio account details: company name, plan status, location, domain and website.get_account_team(read, admin): Get one team on the account with its lead and members.get_account_user(read, admin): Get one user on the account: name, email, role, team and status.get_company_intelligence(read, clients): Get the synthesised Company Intelligence profile (voice, hiring process, values, business, personal attributes, compensation, flags).get_transcription_usage(read, calls): Get the current calendar month's video-transcription usage for your Lovelio account.get_workflow_rules(read, admin): The agency's workflow rules: AI screening thresholds, chase and escalation days, reference counts and auto-reject settings.invite_team_member(write, admin): Invite a new team member by email.list_account_teams(read, admin): List the teams on the account with their members.list_account_users(read, admin): List the users on the account with name, email, role and team.list_api_keys(read, admin): List the API keys on the account: name, key prefix, scopes, status and last use.list_email_templates(read, pipeline): List the agency's email templates with their key, subject and body.reassign_record(write, admin): Move ownership of clients, jobs, candidates, applications, placements, specs or BD targets to another consultant.refresh_company_intelligence(write, clients): Queue a fresh deep-enrich + seven-pass synthesis run for the account.resolve_identity_review(write, pipeline): Decide an identity match Lovelio was not sure about.resolve_occupation_review(write, pipeline): Decide the occupation behind a job title Lovelio could not place on its own.set_quota_target(write, admin): Set (or clear with null) the billings target for the agency, a team or a consultant in a quota period.update_brand_voice(write, admin): Set the agency's brand voice prompt: the tone and phrasing Lovelio uses in agency-authored copy such as candidate emails and social posts.update_workflow_rules(write, admin): Change the agency's workflow rules in two steps.
Activities (activities:read, activities:write)
add_note(write, core): Add a note to any record (job, job ad, candidate, application, interview, client, placement).list_activities(read, core): List recent activity and audit events.log_call(write, core): Record a phone call with a candidate or a client contact (never a bare company: pass client_contact_id and the client resolves from it).
Analytics (analytics:read)
get_analytics_dashboard(read, analytics): The agency's headline numbers: open jobs, active candidates, submissions, interviews and placements for the current period, as the in-app dashboard shows them.get_analytics_metrics(read, analytics): Recruiting metrics over a trailing window in days: time to submit, time to place, conversion between stages, and volumes.get_analytics_pipeline(read, analytics): Pipeline counts by stage over a trailing window in days, for the whole agency or one job.
Applications (applications:read, applications:write)
bulk_reject(write, pipeline): Reject several applications in one go, with one reason for all.complete_assessment(write, pipeline): Fill in the assessment stages the arrival cascade skipped (gated Client Fit, deferred summary) for a candidate on a job.confirm_phone_screen_draft(write, pipeline): Log the phone screen: adds the notes to the timeline, then advance or hold moves an application a human has marked Yes to Screen, reject parks a pre-client candidate at No, and no_show leaves the stage alone.create_application(write, core): Attach an existing candidate (or several) to a job.get_application(read, core): Get full details of an application including AI assessment scores, criteria breakdown, and recommendation.get_phone_screen_draft(read, pipeline): Read a phone screen draft and its remaining gaps.list_applications(read, core): List job applications.list_review_queue(read, pipeline): List the review queue: things Lovelio flagged for a human to look at (stalled candidates, reference red flags, unanswered client questions, interviews due an outcome, and so on), with priority and whether each is resolved.log_offer(write, pipeline): Record that the client has made an offer to the candidate and move the application to the Offer stage.move_stage(write, core): Move an application to any canonical pipeline stage, forwards or backwards.prepare_phone_screen(write, pipeline): Start a phone screen draft for an application.reassess_application(write, pipeline): Queue a fresh candidate assessment for an application after its candidate, job requirements or Client DNA changed.record_offer_outcome(write, pipeline): Record what happened to the offer that is out on an application: accepted (stays at Offer - log the placement next), declined (declined_by candidate moves it to Withdrew, client to No; no email goes to the candidate), or countered (records the candidate request separately from the client terms, stays at Offer).reject_candidate(write, core): Reject an application (moves the application to the rejected stage).resolve_review_item(write, pipeline): Resolve one review queue item, reopen it, change its priority, or snooze it until a date.update_application(write, pipeline): Change the source or owner on an application (not the stage: use move_stage; not notes: use add_note).update_phone_screen_draft(write, pipeline): Set the outcome, notes, call time or duration on a phone screen draft.withdraw_application(write, pipeline): Mark an application as withdrawn (candidate-initiated drop-out).
Recorded calls (calls:read, calls:write)
book_call(write, calls): Book a video call with a candidate or a client contact on the user's own calendar, now or later.discard_call(write, calls): The other person did not agree to the call being recorded: delete the call with its recording, transcript, write-up, clips and timeline entry.do_call_action(write, calls): Do, or dismiss, one of the next steps Lovelio suggested from a call transcript (mark Yes, email the candidate or contact, a reminder, book the next call, save the fee, add a contact, add a hiring DNA note).finish_call_upload(write, calls): Tell Lovelio the recording from start_call_upload has finished uploading.get_call(read, calls): Get a recorded call: who spoke and for how long, the transcript line by line with times, the write-up (summary, strengths, watch-outs, salary, notice, work rights) and a short-lived link to the recording.link_call_to_person(write, calls): Link a phone call that matched nobody by number (or matched two people) to the candidate or client contact on it.list_calls(read, calls): List recorded calls.make_call_clip(write, calls): Keep a stretch of a call transcript as a clip: lines start_seq to end_seq (inclusive, at most 40, seq from get_call).name_call_speaker(write, calls): Say who a voice on a call transcript is ("Speaker 1 is Sam").remove_call_clip(write, calls): Remove a clip from a call.sign_call_upload_parts(write, calls): Signed URLs for parts of a recording started with parts: true.start_call_upload(write, calls): Start uploading a call recording (MP4, MOV, WebM, M4A, AAC, MP3, WAV, OGG or FLAC, up to 2 GB) onto one person: a candidate (or application) or a client contact, or whoever is on calendar_event_id, a meeting in your own diary.
Candidates (candidates:read, candidates:write)
add_candidate_import_files(write, candidates): Get signed upload slots for original CV files.add_referee(write, forms): Add a referee to an application by hand (name and email, optionally relationship and phone).add_web_candidate(write, candidates): Add a person from search_web_candidates to the job.add_web_candidate_to_database(write, candidates): Add a person from search_web_candidates to the database.add_web_candidate_to_pool(write, candidates): Add a person from search_web_candidates to a talent pool instead of the job.add_web_candidates(write, candidates): Add up to 25 people from search_web_candidates to the database in one call.bulk_add_to_talent_pool(write, talent_pools): Add several candidates to a talent pool at once.confirm_candidate_draft(write, candidates): Create the candidate from the draft.create_candidate(write, core): Create a new candidate profile.create_candidate_import(write, candidates): Start a bulk CV import.delete_candidate(write, candidates): Soft-delete a candidate (not GDPR hard delete).draft_contractor_check_ins(write, placements): Draft one ask for everyone on the check-in list who can be reached (up to 50): "Are you free from <date>?" for contracts finishing soon, a "How is it going?" check-in for the quiet ones.draft_extension_asks(write, placements): Draft the extension asks: "Keep <name> past <end date>?" to the client contact and "Want to stay on?" to the contractor, for every contract on list_extension_asks (up to 50 emails) or the placement_ids given (any contract still waiting on a plan or asked).draft_job_availability_asks(write, placements): Draft "Are you free from <start date>?" emails for everyone on a contract job's free list whose free date no person confirmed in the last 7 days and who can be reached.evaluate_candidate_evidence(write, candidates): Check requirements across a selection, talent pool, job applicants, import, full search or the candidate database.extract_candidate_preferences(write, candidates): Read the candidate's recent call notes and timeline notes and propose a preferences patch (preview first; { apply: true, patch } writes the confirmed proposal).finalize_candidate_import(write, candidates): Confirm uploaded CV files and close the upload phase.find_candidate_duplicates(read, candidates): Find candidates that look like the same person (same name with the same phone, LinkedIn or employer, or the same phone or LinkedIn alone), as pairs to review and merge.find_contact_details(write, candidates): Look up contact details for up to 100 people already in the database.find_free_for_job(read, candidates): Who is free for a contract job: everyone who meets its requirements and is free by its start date, best fit first, each with who confirmed the free date and when.get_candidate(read, core): Get full profile of a specific candidate including contact details, skills, and resume data.get_candidate_draft(read, candidates): Read a candidate draft: the extracted fields, parse status and remaining gaps.get_candidate_import(read, candidates): Get import progress, failures and the final report.get_contractor_page_link(write, placements): A fresh link to a contractor's own page, for a text or a chat.get_evidence_evaluation(read, candidates): Read progress and original evidence.get_evidence_pack(read, candidates): Read a compact evidence pack for submission, interview or comparison.list_candidate_papers(read, placements): List the documents on file for a candidate: right to work, police checks, licences, insurance, each with the day it stops counting.list_candidates(read, core): List candidates in your Lovelio account.list_outreach(read, candidates): List outreach messages sent to candidates over email, LinkedIn or WhatsApp, with status (draft, sent, replied, no_response).list_referees(read, forms): List the referees on file for a candidate, whether the agency added them or the candidate supplied them.match_candidates_to_job(read, core): Search the whole candidate database for the best matches to a job, ranked with a short why-them line per candidate.merge_candidates(write, candidates): Merge duplicate records of one person into a primary record.prepare_candidate_from_cv(write, candidates): Parse a CV into a candidate draft.prepare_evidence_pack(write, candidates): Prepare reusable source-grounded evidence for a submission, interview or comparison.record_whatsapp_consent(write, candidates): Record that a candidate agreed to get WhatsApp messages from the agency (agreed true), or no longer does (agreed false).save_paper(write, placements): Record a document the agency has checked for a candidate: right to work, a police check, a licence, insurance.search_candidates(read, core): Search candidates by skills, experience, or qualifications using the shared ranked search.send_availability_asks(write, placements): Send availability asks a person has read: each one goes on its channel (email, the default; or "whatsapp", the approved template, only to someone who agreed, 8am to 6pm on a weekday their time, at most one a week) and records the ask, so their reply sets their free date (source candidate_reply).send_extension_asks(write, placements): Send extension asks a person has read, up to 25 at once: to "client" emails the client contact (client_contact_id, else the contract's contact), to "contractor" emails the contractor, or with channel "whatsapp" sends them the approved WhatsApp ask (only to someone who agreed, 8am to 6pm on a weekday their time, at most one a week).update_candidate(write, core): Update a candidate profile.update_candidate_draft(write, candidates): Fill or correct fields on a candidate draft before confirming.update_candidate_preferences(write, candidates): Update what a candidate wants next: preferred roles, seniority, work types, relocation, availability, preferred locations, salary expectation, notice period and work rights.update_candidate_tags(write, candidates): Add or remove tags on a candidate without touching the rest (pass add and/or remove), or replace the whole list with tags.
Clients (clients:read, clients:write)
add_client_contact(write, core): Add a contact (hiring manager, HR, finance) to a client.add_client_dna_note(write, clients): Add a note to a client's DNA - anything learned from a meeting, call, or feedback (what they value, how they interview, dealbreakers).add_client_recruiter(write, clients): Add a teammate to a client's recruiter list.add_found_client(write, clients): Add a company from find_new_clients as a client, with the people you pick from find_client_people as its contacts.create_client(write, core): Create a client (a company the agency recruits for).create_client_site(write, clients): Add a work site (an address) to a client, for contract placements and job locations.delete_client(write, clients): Delete a client record.delete_client_contact(write, clients): Remove a contact from a client.find_client_duplicates(read, clients): Find clients that look like the same company, as pairs.find_client_people(write, clients): The people worth calling at a company from find_new_clients, grouped into Leadership, Hiring managers and People and talent, most senior first.find_new_clients(write, clients): Find companies the agency could win as clients, from the company database.get_careers_float(read, clients): Get the float drafted for one new careers ad: who fits, who is picked, the anonymous profiles, the contact and the email.get_client(read, core): Get full detail of one client: website, industry, locations, enrichment and Client DNA status, and notes.get_client_dna(read, clients): A client's hiring DNA: how they hire, what they value, deal breakers and the notes consultants have added.get_job_lead(read, clients): Get one watched company: each new ad with the people in your database who fit it, ads outside the patch, and every other open job.list_client_contacts(read, clients): List a client's contacts: name, title, email, phone, who they report to and which one is primary.list_client_sites(read, clients): List a client's sites: the offices and locations jobs can be based at.list_clients(read, core): List the clients in your Lovelio account - the companies the agency recruits for.list_job_leads(read, clients): List the companies a recruiter watches on the Opportunities page, most new jobs first: new jobs from their careers pages, how many people in your database fit, and where each float stands.list_key_movements(read, clients): List open key movements: people you know who changed jobs or titles, past placements on the move, and people you placed or submitted applying again.merge_clients(write, clients): Merge duplicate records of one client company into a primary record.promote_client_contact(write, clients): Turn a "works here" person - someone whose own record says they work at this client - into a contact the agency deals with.remove_client_recruiter(write, clients): Remove a teammate from a client's recruiter list.remove_client_site(write, clients): Remove a work site from a client.set_client_fee_terms(write, clients): Set a client's fee schedule: currency, fee basis (package or base), slab tiers (each band's percent applies to the whole salary), minimum fee, contract margin, guarantee days and remedy, payment terms.update_client(write, clients): Update fields on a client (name, website, company phone, industry, description, locations, address, notes).update_client_contact(write, clients): Update a contact on a client (name, email, phone, title, notes, primary, reports-to).
Documents (documents:read, documents:write)
delete_document(write, candidates): Delete an uploaded document.get_document(read, candidates): Get one document record: file name, content type, size, which record it belongs to and who added it.get_document_download_url(read, candidates): Get a short-lived signed URL to download one document.list_documents(read, candidates): List the documents attached to one record (a candidate, application, client, job or placement): CVs, cover letters, contracts and uploads, with file name, type and when each was added.set_candidate_cv(write, candidates): Make one of the candidate's uploaded documents their current CV (the file the profile points at and the one that is parsed).upload_document(write, candidates): Attach a file to a record (candidate, client, contact, job, application, placement and so on).
Comms (emails:read, emails:write)
bulk_send_email(write, pipeline): Send the same email to several candidates now.cancel_scheduled_email(write, pipeline): Cancel a scheduled email before it sends.confirm_email_draft(write, pipeline): Send the drafted email now, or schedule it if the draft has a schedule_at.email_client_contact(write, clients): Send an email to a contact at a client now.get_email_draft(read, pipeline): Read the current state of an email draft: subject, body, schedule and any gaps left before it can be sent.get_scheduled_email(read, pipeline): Get one scheduled email: recipient, subject, body, send time and status.list_scheduled_emails(read, pipeline): List emails queued to send later, with status, recipient candidate and send time.prepare_compose_email(write, pipeline): Draft an email to a candidate.revise_email_draft(write, pipeline): Rewrite an email draft from a plain-English instruction, for example "warmer tone, mention the start date".schedule_email(write, pipeline): Schedule an email to a candidate for a future time.send_email(write, core): Send an email to a candidate immediately.update_email_draft(write, pipeline): Set fields on an email draft directly (subject, body, schedule).
Forms (forms:read, forms:write)
add_form_question(write, forms): Add a question to a reusable form template.create_form(write, forms): Create a reusable form template.create_reference_instance(write, forms): Prepare a reference questionnaire for a phone call or later email delivery.delete_form(write, forms): Delete a form template after explicit user approval.delete_form_question(write, forms): Delete a template question after explicit user approval.get_form(read, forms): Get one form template with its questions.get_form_instance(read, forms): One form instance in full: type, status, who it went to and every timestamp from sent to completed.list_form_instances(read, forms): List screening forms, reference questionnaires and interview forms that have been sent or completed, newest first.list_forms(read, forms): List the agency's form templates (screening, reference and interview forms) with their question counts.prepare_screening_form(write, forms): Load or generate the phone screen question set for an application.request_referee_details(write, forms): Email the candidate a link to supply their referees.review_reference(write, forms): Record the consultant's verdict on a completed reference: clear, or concern with a note.send_form(write, forms): Send any form template to a candidate or a client contact, optionally tied to an application, interview or job.send_form_instance(write, forms): Email a prepared reference form instance to its recipient.send_referee_questionnaires(write, forms): Send the reference questionnaire to every referee on an application who has not had it yet, or to the referee ids given.send_reference_request(write, forms): Send one referee the reference questionnaire for an application.send_references_to_client(write, forms): Email completed references for an application to the client contact.send_screening_form(write, forms): Create a screening form for an application and email it to the candidate to fill in.submit_form_instance(write, forms): Save actual respondent answers or complete a phone reference or screening.update_form(write, forms): Update a form template name, description or AI augmentation setting.update_form_question(write, forms): Edit or reorder a form template question.
Integrations (integrations:read)
get_slack_integration(read, admin): The Slack connection for this account: whether it is connected, the workspace, and the channel Lovelio posts to.list_chat_integration_users(read, admin): List the people linked to one chat integration and which Lovelio user each maps to.list_chat_integrations(read, admin): List chat integrations (Slack, WhatsApp and similar) connected to the account, with status.
Interviews (interviews:read, interviews:write)
add_interview_link(write, interviews): Add the meeting link (Teams, Zoom, Meet) to a booked interview, usually a client interview the client is running on their own meeting and sent the link for after the booking.cancel_interview(write, interviews): Cancel a scheduled interview.cancel_meeting(write, interviews): Cancel a previously booked meeting.confirm_interview_action_draft(write, interviews): Apply the reschedule or cancel: updates the interview, the calendar event and emails the candidate if asked.confirm_interview_draft(write, interviews): Book the interview from the draft and put it on the interviewer's connected calendar.confirm_interview_form_draft(write, interviews): Submit the interview form and record the interview outcome from it.confirm_scorecard_draft(write, interviews): Submit the scorecard.create_reminder(write, interviews): Put a reminder in the diary at a specific time, optionally linked to a candidate, job or application.get_calendar_event(read, interviews): Get one calendar event with its time, type, attendees and the record it belongs to.get_interview(read, interviews): Get full details of an interview including feedback, notes, and outcome.get_interview_action_draft(read, interviews): Read a reschedule or cancel draft and its remaining gaps.get_interview_context(read, interviews): Everything an interviewer needs before an interview: the candidate, the job, the interview plan and questions, earlier scorecards and notes.get_interview_draft(read, interviews): Read an interview draft: the fields so far, the predicted values and the remaining gaps.get_interview_form_draft(read, interviews): Read an interview form: sections with scores and notes, the recommendation, feedback and the remaining gaps.get_scorecard_draft(read, interviews): Read a scorecard draft: scores so far, recommendation, feedback and the remaining gaps.list_calendar_events(read, interviews): List calendar events (interviews, phone screens, reminders, deadlines, meetings) in a date window.list_interviews(read, core): List scheduled interviews.prepare_cancel_interview(write, interviews): Start a cancel draft for a booked interview.prepare_interview_form(write, interviews): Generate the AI interview form for an interview, or return the existing one.prepare_reschedule_interview(write, interviews): Start a reschedule draft for a booked interview.prepare_schedule_interview(write, interviews): Start an interview draft for an application.prepare_submit_scorecard(write, interviews): Start a scorecard draft for an interview.propose_interview_slots(read, interviews): Return ~12 candidate day/time options that are free for all of the given interviewers, sourced from the Lovelio calendar (and Google free/busy when connected).request_interview(write, interviews): Book an interview, two ways.reschedule_interview(write, interviews): Reschedule an interview to a new time.reschedule_meeting(write, interviews): Move a previously booked meeting to a new time.schedule_call(write, interviews): Book a phone call in the diary (with a candidate, or internal).schedule_interview(write, core): Schedule an interview for an application.schedule_meeting(write, interviews): Schedule a meeting (generic calendar event).send_interview_followup(write, interviews): Send a post-interview follow-up: recipient "candidate" for the debrief / news / no-show note, recipient "client" for the next-steps or feedback note to the client contact (client interviews only).submit_scorecard(write, interviews): Submit an interview scorecard: per-criterion scores, an overall recommendation, and optional notes.update_interview_action_draft(write, interviews): Change the new time, reason or notify flag on a reschedule or cancel draft.update_interview_draft(write, interviews): Fill or change fields on an interview draft before confirming.update_interview_form_draft(write, interviews): Score one section (section_id plus section_score 1 to 5 and notes), or set the overall recommendation and feedback on an interview form.update_interview_outcome(write, interviews): Record the outcome (passed / failed / no_show / cancelled) and optional feedback for an interview.update_scorecard_draft(write, interviews): Score criteria on a scorecard draft.
Jobs (jobs:read, jobs:write)
check_talent_pools(write, talent_pools): Check a live job against the talent pools once more.close_job(write, jobs): Close a job.confirm_job_draft(write, jobs): Promote a staged draft from create_job_from_description into a real job.convert_spec(write, clients): The client wants a specced candidate: create the job at that client and add the candidate at the submitted stage in one step.create_distribution_rule(write, jobs): Create a distribution rule: jobs matching the filter post automatically to the named boards when published.create_job(write, core): Create a new job requisition.create_job_ad(write, jobs): Create a new public job ad for a role.create_job_from_description(write, jobs): Turn a plain-language description of a role ("senior backend engineer in London, 120k") into a drafted job spec.delete_job(write, jobs): Soft-delete a job that has no job ads or applications yet.draft_job_from_call(write, calls): Turn the role a client briefed on a call into a job draft, client and contact already set.get_job(read, core): Get full details of a specific job including description, assessment criteria, interview questions, and pipeline stats.get_job_ad(read, jobs): Fetch the most recent published job ad for a job.get_job_ad_by_id(read, jobs): Get one job ad by its own id: the copy, status, publish dates and the job it belongs to.get_job_draft(read, jobs): Read the current state of an in-progress job draft.get_job_ranking(read, jobs): Stack-rank every interviewed candidate on a job by aggregate scorecard score (highest first).get_job_share_bundle(read, jobs): Get the ready-to-share content bundle for a job: pre-composed X post, LinkedIn post, careers URL, image URL, and a short email summary.get_job_summary(read, jobs): Get an AI-generated recruitment summary for a job - pipeline health, top candidates, and recommended actions.get_social_draft(read, jobs): Fetch the auto-generated LinkedIn + X social post drafts for a job, plus the OG image URL used for unfurl previews.list_distribution_boards(read, jobs): List the job boards Lovelio can post ads to, with whether each is connected for this account.list_distribution_postings(read, jobs): List where job ads are posted: one row per ad per board, with status and the live URL.list_distribution_rules(read, jobs): List the job-board distribution rules: which jobs post automatically to which boards.list_job_ads(read, jobs): List job advertisements.list_jobs(read, core): List jobs in your Lovelio account.plan_linkedin_sourcing(write, jobs): Build four LinkedIn sourcing searches from a job's full structured requirements, prior placement signals and search feedback: direct, adjacent, feeder-company and hidden-gem lanes.reopen_job(write, jobs): Reopen a closed or filled job (sets status=active).revise_job_draft(write, jobs): Apply a natural-language instruction ("make it punchier", "move to Sydney and bump salary to AUD 180k", "more formal tone") to a staged draft.rewrite_job_ad(write, jobs): AI-rewrite an existing job ad with a natural-language instruction.search_web_candidates(write, core): Find people on the web who are not in the agency yet.skip_job_draft_gap(write, jobs): Record that a job draft gap is deliberately left blank so it stops being reported.update_distribution_rule(write, jobs): Change a distribution rule: rename it, change its boards or filter, or pause and resume it with active.update_job(write, core): Update fields on an existing job (title, location, compensation, status, team, hiring manager).update_job_ad(write, jobs): Edit a job ad (title / description / published flag).update_job_draft(write, jobs): Fill one or more gaps on an in-progress job draft - including the mandatory client_id (cli_..., from list_clients / create_client).update_social_draft(write, jobs): Edit a job's LinkedIn / X post text.
Marketplace (marketplace:read, marketplace:write)
get_marketplace_deal(read, marketplace): One marketplace deal in full: counterpart agency, split, and the linked application and placement once they exist.get_marketplace_intro(read, marketplace): One marketplace intro in full, including the anonymous candidate profile until the intro is accepted.get_marketplace_listing(read, marketplace): One marketplace listing in full: pitch, chips, salary, fee split and expiry.list_marketplace_deals(read, marketplace): List marketplace deals (accepted intros) with their status and your share of the fee.list_marketplace_intros(read, marketplace): List marketplace intros: submissions of your candidates to other agencies' jobs, requests for other agencies' candidates, and the ones sent to you.list_marketplace_listings(read, marketplace): Browse marketplace listings: jobs other agencies want candidates for and candidates other agencies will share, on a split fee.request_marketplace_candidate(write, marketplace): Ask the agency behind a marketplace candidate listing for an intro.respond_to_marketplace_intro(write, marketplace): Respond to a marketplace intro: check whether you already know the candidate, accept (reveals both sides and opens a deal), decline with a reason, or withdraw one you sent.send_marketplace_listing(write, marketplace): Email one of your marketplace listings to a recruiter at another agency who is not on Lovelio yet, with a link they can respond from.share_candidate_to_marketplace(write, marketplace): List one of your candidates on the marketplace, anonymously, so other agencies can request an intro on a split fee.share_job_to_marketplace(write, marketplace): List one of your jobs on the marketplace so other agencies can submit candidates on a split fee.submit_to_marketplace_job(write, marketplace): Submit one of your candidates, anonymously, to another agency's marketplace job.update_marketplace_deal(write, marketplace): Move a marketplace deal along: add the shared candidate to one of your jobs' pipelines, mark your side of the fee paid or received, or close the deal with a reason.update_marketplace_listing(write, marketplace): Pause, resume, withdraw, renew or mark filled one of your marketplace listings.
Placements (placements:read, placements:write)
create_placement(write, core): Record a placement (the win): a candidate placed with a client for a fee.draft_availability_check(write, placements): Draft an email asking a contractor whose contract ends in the next eight weeks if they are free from the first working day after it ends.get_contractor_report(read, placements): The contract forecast: live contracts with GP week by week for the next 13 weeks on the agency's standard week, less the public holidays where each contractor works, added up for the agency, each team and each consultant, plus the contractors with documents to chase.get_placement(read, placements): Get one placement in full: candidate, client, job, salary, fee, start date, and guarantee.get_placement_papers(read, placements): Check a contract's documents: what it needs (from the job, the pay route and the country's rules) and whether each is current, missing, expired, ending before the contract does, or marked not needed.list_contractor_check_ins(read, placements): The contractors to check in with this week: contracts finishing within 8 weeks with no confirmed free date, and contractors not heard from in 30 days.list_extension_asks(read, placements): Contracts ending within 8 weeks with no plan and nobody asked yet about extending, each with the client contact who would be asked.list_placements(read, core): List placements - candidates placed with a client for a fee.mark_placement_status(write, placements): Update a placement through its guarantee lifecycle: pending_start, started, fell_off (during guarantee), or completed.send_placement_email(write, placements): Send a placement lifecycle email: recipient "candidate" for the start-day good-luck note, recipient "client" for the guarantee-end check-in.send_placement_to_xero(write, placements): Make or update a perm placement's DRAFT invoice in the agency's Xero, for the fee on the placement.set_candidate_contracting_profile(write, placements): Save a candidate's contracting details for their country so contract placements pick them up.set_placement_credits(write, placements): Replace who gets credit for a placement and how the fee is split between them.set_placement_payroll_reference(write, placements): Pick the payroll partner's Position and Rate template for a contract placement, using the partner's own ids from the lists they pushed.set_placement_timesheet_approvers(write, placements): Set the client contacts who approve timesheets on a contract placement.update_placement(write, placements): Change the terms on an existing placement: salary and package, fee, dates, contract rates and end date, the contract plan (what happens on the end date), the next contact date, expected GP, work site (work_site_id, null to clear), timesheet approvers, the billing fields (hiring manager, invoice contact, PO number, invoice notes), contractor details, or credits.waive_paper(write, placements): Mark one document not needed for one contract (waived true), or ask for it again (waived false).
Quotas (quotas:read)
get_quota_board(read, analytics): A quota period's board: recognised billings, placement counts and targets for the agency, each team and each consultant, plus any adjustments.get_quota_plan(read, analytics): The agency's quota plan: cadence, currency, when a placement counts, which placement types count, and whether closed periods are locked.list_quota_periods(read, analytics): List quota periods (the quarters, months or years the agency measures billings in), newest first.
Submissions (submissions:read, submissions:write)
answer_spec_question(write, clients): Reply to a question the client asked from a spec profile page.answer_submission_question(write, clients): Answer a question a client asked about a submitted candidate, or send them any message on that submission.chase_spec(write, clients): Nudge the client contact about a spec they have not responded to.chase_submission(write, clients): Nudge a client contact about a submission they have not responded to.create_submission(write, core): Send a submission of candidates on a job to the job's client contact for review.get_spec(read, clients): Get one spec send: the candidate, the client, the message sent and how the client responded.get_submission(read, clients): Get one submission in full: the client review link, each candidate, and the client verdict, rating, and note per candidate.list_specs(read, clients): List spec sends: candidates floated to a client without a live job, with status (sent, viewed, interested, passed, converted).list_submissions(read, core): List submissions - packs of candidates sent to a client contact for review.record_client_verdict(write, clients): Record what the client said about a submitted candidate when they told you directly instead of using their review link: interview_requested, or rejected with an optional reason, their words, and whether they want more candidates.send_spec(write, clients): Send a spec (a float): pitch one candidate, or up to five in candidates, to a client contact with no open job.withdraw_spec(write, clients): Take back a spec the client still has open (sent, viewed or interested, not yet turned into a job).
Talent pools (talent_pools:read, talent_pools:write)
add_to_talent_pool(write, talent_pools): Add a candidate to a talent pool.ask_pool_member_if_looking(write, talent_pools): Email a talent pool member the pool's "still looking?" check-in now, as you.create_talent_pool(write, talent_pools): Create a talent pool - a named group for candidates who are a good agency fit.delete_talent_pool(write, talent_pools): Permanently delete a talent pool.get_talent_pool(read, talent_pools): Get one talent pool: name, description and eligibility rule.list_talent_pool_members(read, talent_pools): List the candidates in one talent pool, with when and how each joined.list_talent_pools(read, talent_pools): List talent pools with name, slug and eligibility.remove_from_talent_pool(write, talent_pools): Remove a candidate from a talent pool.set_pool_member_interest(write, talent_pools): Record whether a talent pool member is still looking, when they told you some other way (a call, a text).
Webhooks (webhooks:read, webhooks:write)
create_webhook(write, admin): Create a webhook subscription.delete_webhook(write, admin): Delete a webhook subscription.get_webhook(read, admin): Get one webhook endpoint: URL, events, status and recent delivery health.list_webhook_deliveries(read, admin): Delivery log for one webhook, newest first: event, status, attempts, last response code and error.list_webhook_events(read, admin): List every event type a webhook can subscribe to, with what each one means.list_webhooks(read, admin): List the webhook subscriptions on this account with their URLs, events and status.replay_webhook_delivery(write, admin): Queue a repeat delivery of an existing event.rotate_webhook_secret(write, admin): Replace a webhook signing secret after operator approval.test_webhook(write, admin): Send a signed test event.update_webhook(write, admin): Change a webhook destination, events or active/paused status.
Open to every grant
batch(write, core): Run up to 100 Lovelio write actions in one call, including every write with no typed tool on this connection.describe_action(read, core): Describe one batch action: what it does, the payload shape and the scope it needs.discard_draft(write, core): Throw away a draft you will not confirm, by its review token (edr_, idr_, iad_, psd_, scd_, cdr_ or jdr_).get_job_creation_status(read, jobs): The job-creation view of get_task: the same task, shown as per-step progress (description, criteria, questions, ad, social).get_task(read, core): Check an async task started by another call (a new job being written, an enrichment, an import, a bulk job): its status, progress and result when done.list_actions(read, core): List Lovelio actions with the payload shape and the scope each needs.list_stages(read, core): List the pipeline stages in order, with the key and label of each.resolve_command(read, pipeline): Resolve an everyday request to a Lovelio page, record or existing process.search(read, core): Search one record type (jobs, candidates, applications or interviews) in plain English, such as "jobs in Sydney" or "applications from last week".
Draft flows
Every prepare_* tool stages a draft and returns a review_token, the draft so far, and gaps, the fields still missing. get_*_draft reads it back, update_*_draft sets fields (the email flow also has revise_email_draft for a plain-English change), and confirm_*_draft performs the real write: sends the email, creates the candidate, books the interview, logs the call, submits the scorecard. Confirm refuses with a 409 draft_has_gaps error that lists the gaps until every one is filled, so an agent can loop on the error text. Booking an interview can also refuse with SCHEDULING_CONFLICT, carrying the clashes and suggested slots; retry with a new scheduled_at or ignore_conflicts: true. Nothing changes in Lovelio until confirm, and none of these flows moves a stage on the AI's own verdict: a phone screen moves the application only on the outcome the caller sets.
Sending email
Outbound email is off by default on every new account, including agent-created trials. Tools that send email return a clear error until Lovelio enables outbound for your account. This is a safety rail, not a bug; contact hello@lovelio.ai to enable it.
Rate limits
Each underlying V1 request consumes one request against your key's rate limit (60 per minute on trial, 600 per minute on paid). The OAuth endpoints are rate limited per IP. See error handling for retry rules.
Troubleshooting
- Cannot approve access. Sign in as an admin of the intended workspace. Older connections using coarse read/write grants need one reconnect.
- 401 "Invalid or expired access token". Your refresh token has expired (30 days) or the connection was revoked. Reconnect: your client will open the authorize page again.
- OAuth loops. Clear the cached Lovelio credentials in your MCP client and reconnect. Local dev builds should point at
http://localhost:3005/api/mcp. - "Invalid or expired credential" right after connecting. You are probably connected to another region's URL - a connection made on one region's domain never works on another. Reconnect with
https://lovelio.ai/api/mcp. - Rate limit 429. Back off and retry after the
Retry-Afterinterval.
Tool results and safe retries
Check isError before treating a tool call as successful. Successful results set isError: false and structuredContent.success: true. Their existing human-readable content[].text stays unchanged. Asynchronous success means the task was accepted; poll its task ID for completion.
Failures set isError: true and structuredContent.success: false. Read structuredContent.error.code, not the wording of the text:
{
"isError": true,
"content": [{ "type": "text", "text": "This call needs Write clients (clients:write). Add it to the key or the app's scopes.\nINSUFFICIENT_SCOPE: Resolve the reported problem before trying again." }],
"structuredContent": {
"success": false,
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This call needs Write clients (clients:write). Add it to the key or the app's scopes.",
"status": 403,
"category": "permission",
"retry": "none",
"guidance": "Resolve the reported problem before trying again."
}
}
}
The API code and message survive unchanged. Failure details retain field, docs, request_id, error data, retry_after, operation_id and idempotency_key when available. status: 0 means no HTTP response arrived. Categories include permission, authentication, invalid_input, not_found, conflict, rate_limit, transient, in_progress, uncertain_outcome and failure. Unknown codes remain errors.
error.retry | Agent action |
|---|---|
none | Resolve the permission, input or resource problem first. |
retry | Wait for retry_after, or use bounded backoff. |
same_key | Repeat only the identical operation with the original idempotency_key. This asks R1 for the saved result or current claim state. |
reconcile | Read the resource to establish the outcome. Do not repeat the write, use a new key, or wait for expiry. |
Before a write, save a unique idempotency_key in the tool arguments. If omitted, the server creates one for POST requests and includes it in the success or error details. If the MCP response itself disappears and you did not save a key, reconcile first. The server does not retry automatically. PATCH requests and draft streaming do not gain retry protection from this argument.
IDEMPOTENCY_IN_PROGRESS and IDEMPOTENCY_UNAVAILABLE allow only same_key. IDEMPOTENCY_RESULT_STORAGE_FAILED also allows only same_key; the outcome remains uncertain. IDEMPOTENCY_OUTCOME_UNKNOWN requires reconciliation. A key mismatch (IDEMPOTENCY_KEY_REUSED) never means the changed operation succeeded. Inspect the original operation before choosing the next action. Completed responses last 24 hours; uncertain executions remain reserved beyond expiry.
batch retains every operation in structuredContent.operations, including each result or typed error and its original correlation ID. Any failed operation sets the whole result to isError: true (BATCH_PARTIAL_FAILURE or BATCH_FAILED). The text still starts with the succeeded/failed counts and per-operation lines. Follow each failed operation's retry guidance and retain its own key. Never replay successful operations with new keys.
Permissions in discovery
tools/list returns tools whose required permissions fit the current OAuth grant and enabled modules. V1 still enforces each call, including record access and conditional requirements. Discovery cannot grant authorization. An old narrow grant remains narrow if its underlying key gains permissions; reducing or revoking access also affects subsequent calls.
Tool descriptions state required permissions. add_to_talent_pool requires talent_pools:write; omitting pool_id also requires talent_pools:read for lookup. Supplying an ID avoids that lookup. Batch descriptions derive each action's scope from the existing V1 action declarations; each operation receives its own permission check. A visible batch tool does not authorize every action.
Account actions that first resolve the account need accounts:read as well as accounts:write. Personal MCP OAuth can request accounts:write; Connect partner apps cannot. The connected person must also have the required role permission. Person-bound and app-install credentials cannot mint API keys, even with accounts:write. Financial fields retain the V1 field-level permission checks. Search currently requires candidates:read for every supported entity type, matching the existing API declaration.