100Hires logo

100Hires

0

Official 100Hires ATS plugin — manage candidates, jobs, applications, interviews, and messages from your AI editor. 131 MCP tools, OAuth auth, no API key setup.

1 rule

# 100Hires ATS — rules for AI agents You have access to the 100Hires MCP server (131 tools). The rules below match the actual tool schemas — never guess parameter names, enums, or formats. When a value isn't in the rules, call the corresponding `hires_list_*` tool to discover it. ## Authentication The server uses OAuth 2.0 (RFC 8414 / RFC 9728 + PKCE + Dynamic Client Registration). The editor opens a browser on first use. If a tool call returns 401, ask the user to re-authenticate via the editor's plugin settings. ## ID vs alias | Entity | Accepts | |---|---| | Candidate | numeric ID **or** string alias (param `id`) | | Job | numeric ID **or** string alias (param `id`) | | Application | **numeric ID only** (param `id`) | | Notes, interviews, messages, evaluations, forms, templates, webhooks | numeric ID only | When the user provides a name (not an ID), first call `hires_list_candidates(q=...)`, `hires_list_jobs(...)`, or `hires_list_users(search=...)` to resolve to an ID. ## Date / time formats - **Unix timestamp in seconds (integer)** — used everywhere except as noted below: `scheduled_at`, `start_time`, `end_time`, `send_at`, `date_from`, `date_to`, `created_after`, `updated_after`, `since`, `until`, `moved_at`. - **`YYYY-MM-DD` string** — only `hires_list_interviews(date=...)`. - **ISO 8601 duration** — only `assign_task` nurture step `due_in_interval` (e.g. `"P3D"`). - **Never ISO datetime strings** for scheduling. ## File / attachment payloads All file fields (`cv`, `resume`, `file`, voicemail, company logo) take the SAME object shape — **never** a URL or file path: ```json { "data": "<base64>", "file_name": "resume.pdf", "mime_type": "application/pdf", "size": 123456 } ``` `size` is optional. `data`, `file_name`, `mime_type` are required. ## Read before write Before any destructive call (`delete_*`, `disqualify_candidate`, `batch_reject_*`, `batch_move_*`, `reject_application`, `hire_application`): 1. Call the corresponding `list_*` or `get_*` to confirm state. 2. Show the user what will change. 3. Then mutate. `hires_disqualify_candidate` rejects **all active applications** for the candidate — confirm with the user explicitly. ## Candidates - `hires_create_candidate` — every field is optional (`first_name`, `last_name`, `email`, `phone`, `profile`, `job_id`, `stage_id`, `cv`). If `stage_id` is set, `job_id` is also required. - `profile` field: object of `{question_text_or_id: string_value}` — record of strings, not nested objects. - `hires_list_candidates` filters: `job_id`, `stage_id`, `email` (exact), `q` (partial name/email), `full_name`, `linkedin`, `created_after`, `updated_after` (Unix seconds), `include` (string), `page`, `size`. - `hires_add_candidate_tags(tags=["..."])` — array of **tag strings**, not IDs. - `hires_remove_candidate_tag(tag="...")` — singular `tag` string, not array. - `hires_batch_add_tags(ids=[1,2], tags=["a","b"])` — `ids` are **numeric only** (no aliases). Max 100. - `hires_list_candidate_activities(event_type=...)` — comma-separated string from: `comment`, `copilot_response`, `stage_moved`, `automation_action_triggered`, `assign_job`, `enrichment`, `call`, `validate_emails`, `profile_mutation`, `qualification`, `assign_tags`, `assign_sources`, `candidate_rate`. - `hires_list_candidate_messages(is_scheduled=1)` — literal integer `1`, not boolean `true`, not string `"1"`. - `hires_disqualify_candidate(reasons=[id...])` — optional. IDs from `hires_list_rejection_reasons`. - `hires_get_candidate_resume(include="text_content")` — adds `text` field with parsed plain text; no download needed. ### Sending messages `hires_send_candidate_message(id=<candidate>, to=[...], subject, body, scheduled_at?, from_account_id?, reply_to_email_id?, send_in_new_thread?)`: - **Required:** `to` (array of strings), `subject`, `body` (HTML). - `scheduled_at` — Unix seconds. Defaults to **created_time + 900 seconds (15 min)** if omitted. - `from_account_id` — numeric mail account ID. **There is no `from_user_id` param.** To find an account ID: - `hires_get_user(id).default_mail_account_id`, OR - `hires_list_user_mail_accounts(id=<user_id>)`, OR - `hires_list_company_mail_accounts` (current company), OR - `hires_list_company_id_mail_accounts(id=<company_id>)`. - `reply_to_email_id` — mailbox message ID for threading. **Not** `reply_to_message_id`. ## Applications `id` is **numeric only**. Aliases not accepted. - `hires_create_application(candidate_id, job_id, stage_id?)` — `candidate_id` accepts alias or number, `job_id` is numeric. `stage_id` optional (defaults to first stage). - `hires_list_applications` filters: `candidate_id`, `job_id`, `stage_id`, `status` (`"pending"` | `"hired"` | `"rejected"`), `sort` (`"created_at"` | `"-created_at"` | `"ai_score"` | `"-ai_score"`), `include` (`candidate`, `cv.text` — comma-separated). - `hires_move_application(id, stage_id)` — `stage_id` is **required**, sets a specific stage. Use `hires_list_workflow_stages(job_id=...)` or `hires_get_workflow_stages(id=<workflow_id>)` to discover IDs. - `hires_advance_application(id)` — no `stage_id`; system picks the next stage from the workflow. - `hires_hire_application(id)` — no body params. - `hires_reject_application(id, rejection_reason_id?, suppress_notification?)` — both optional. `rejection_reason_id` from `hires_list_rejection_reasons`. - `hires_transfer_application(id, job_id, stage_id?)` — creates a **new** application on target job. - `hires_batch_move_applications(ids=[...], stage_id)` — max 100, numeric IDs only. - `hires_batch_reject_applications(ids=[...], rejection_reason_id?)` — max 100. - `hires_list_application_stage_history(id)` — returns `from_stage_id`, `to_stage_id`, `moved_at` (Unix seconds), `moved_by_type` (`system` | `user` | `automation`), `moved_by_user_id`. - `hires_get_ai_score(id)` — returns null if not yet AI-scored. - `hires_list_application_evaluations(id)` — returns evaluation form IDs. Use those with `hires_get_evaluation(id=...)`. - `hires_upload_application_attachment(id, file={data,file_name,mime_type,size?})` — param is `file`, not `attachment`. ### Creating interviews `hires_create_interview` lives in **applications.ts** (despite the name) and is called via `POST /applications/{id}/interviews`. **Schema:** - `id` — application ID (number, **path param**, not a body field). - `start_time` — Unix seconds (required). - `end_time` — Unix seconds (required). - `interviewer_ids` — array of user IDs (required). **Not** `interviewer_user_id` (singular). **Not** a string. - `location` — optional string. - `include` — `candidate`, `application`, `job` (comma-separated). **There is no `scheduled_at`, `duration_minutes`, `type`, or `interviewer_user_id` field on this tool.** ## Jobs - `hires_create_job` — required: `status`, `title`, `description`, `location_city`, `location_country`. Everything else optional. - `status` is a **free string** (label like `"Public"`, `"Draft"`, `"Archived"`). Get valid values from `hires_list_statuses`. - `resume_field_status`: `"required"` | `"optional"` | `"hidden"`. - `salary_period`: `"annually"` | `"monthly"` | `"daily"` | `"hourly"`. - `knockout_questions[]` — each item: `text` (string), `expected_answer` (`"Yes"` | `"No"`), `disqualify_on_wrong_answer` (boolean). All three required per item. - `ai_scoring_criteria[]` — each item: `id` (optional, for updates), `title` (optional), `text` (required), `weight` (required, int 1–10). **Diff-replace semantics**: items without `id` are created, items with `id` are updated, existing criteria not in the payload are removed. Pass `[]` to remove all. - Workflow / form auto-creation: if `workflow_id` is omitted on create, a new workflow named after the job title is created with default stages. Same for `form_id`. - `hires_set_job_status(id, status)` — separate **PATCH** endpoint from `hires_update_job`. - `hires_publish_to_job_board(id, boards?)` — `boards` is array of string identifiers from `hires_list_boards` (e.g. `["indeed", "linkedin"]`). Optional. - `hires_batch_publish_to_boards(jobs=[...], boards?)` / `hires_batch_remove_from_boards(jobs=[...], boards?)` — `jobs` is array of numeric job IDs. - `hires_add_hiring_team_member(id, user_id)` — one user at a time. - `hires_create_job_webhook` / `hires_delete_job_webhook` — `url` must be HTTPS. `hires_delete_job_webhook` takes both `id` (job ID) and `webhook_id`. ## Workflow stages Two ways to get stage IDs for a job: - `hires_list_workflow_stages(job_id=<job_id>)` — direct. - `hires_get_workflow_stages(id=<workflow_id>)` — when you already know the workflow. Param is `id`, not `workflow_id`. Stage IDs feed `hires_move_application(stage_id)`, `hires_batch_move_applications(stage_id)`, and nurture campaign `move_to_next_stage` steps. **Stage IDs differ per workflow** — never reuse a stage_id across jobs without verifying. ## Messages (outbound mail) - `hires_list_messages(from_account_id, status?, date_from?, date_to?)` — `from_account_id` is **required**. `status`: `"scheduled"` | `"sent"` | `"all"`. Dates are Unix seconds. - `hires_update_message(id, to, subject, body, scheduled_at?, ...)` — PUT, full replace. `to`/`subject`/`body` required. - `hires_patch_message(id, ...)` — PATCH, all fields optional. - `hires_delete_message(id)` — cancels a scheduled message; cannot cancel already-sent. - `hires_batch_create_messages(messages=[...])` — max 100. Each item: `candidate_id`, `to`, `subject`, `body` (required); `scheduled_at`, `from_account_id`, `reply_to_email_id`, `send_in_new_thread` optional. ## Notification messages Separate from outbound messages — these are system emails (rejection notifications, etc.). - `hires_update_notification_message(id, subject, body, scheduled_at?)` — only works for scheduled (not yet sent) notifications. `subject` and `body` required. - `hires_cancel_all_notification_messages(candidate_id)` — cancels every pending notification for that candidate. Returns success even if none exist. ## Nurture campaigns `hires_create_nurture_campaign(title, steps)` and `hires_update_nurture_campaign(id, title, steps)` — `title` is **required** in both. **Update semantics**: send the entire `steps` array. Steps with `id` are updated, without `id` are created, marked `is_deleted=true` are removed. **Every step requires** `delay_days` (≥0) and `send_condition` (`"if_no_reply"` | `"if_no_reply_but_opened"`). `type` selects the variant: | `type` | Required step-specific fields | |---|---| | `email` | `sender: {type: "account"|"user", id: number}`, `template_id` (number) | | `sms` | `sender_user_id` (number), `template_id` (number) | | `voicemail` | `attachment_uuid` (from `hires_upload_attachment(category="voicemail")`) | | `move_to_next_stage` | `stage_id` (number) | | `assign_tag` | `tag_id` (number — **not** tag string) | | `assign_task` | `task_template_id` (number), `assignees` (array of user IDs). Optional: `due_in_interval` (ISO duration), `priority` (number) | ## Attachments - `hires_download_attachment(url)` — `url` must be absolute and **same-host as the configured 100Hires API** (other hosts rejected to prevent Bearer leakage). Max 25 MB. Returns `{file_name, mime_type, size, data: base64}`. - `hires_upload_attachment(category, file, object_id?)` — `category` enum: `"voicemail"` | `"candidate"` | `"application"` | `"candidate_comment"` | `"job_note"` | `"company_favicon"` | `"company_header"` | `"company_link_preview"`. `object_id` is **omitted for `voicemail`**, required otherwise. Voicemail upload returns a `uuid` usable as `attachment_uuid` in nurture steps. ## Notes - `hires_create_note(candidate_id, body, visibility?, mention_user_ids?, user_id?)` — `candidate_id` and `body` (HTML) required. `visibility`: `"all"` (default) or `"private"`. `mention_user_ids` notifies users via @-mention. - `hires_update_note(id, body?, visibility?)` — both body and visibility optional. ## Forms - `hires_create_form(name, questions?)` — `questions` is array of **question IDs** (numbers from `hires_list_questions`), not question objects. - `hires_update_form(id, name, questions?)` — `name` is required even on update. - `hires_update_form_question(form_id, question_id, status)` — `status`: `"required"` | `"optional"` | `"hidden"`. ## Email templates - `hires_create_email_template(name, subject, body)` — all three required. - `hires_update_email_template(id, ...)` — all fields optional; only provided fields are overwritten. - Placeholders: `hires_list_template_placeholders(type)` (enum: `"profile_field"` | `"job_variable"` | `"questionnaire_link"` | `"self_scheduling_link"`) → `hires_prepare_template_placeholders` → insert returned HTML tag into `body`. ## Companies - `hires_create_company` required: `name`, `company_owner_email`, `company_owner_name`. Logo is base64 object. - `hires_list_company_mail_accounts` — no `id` param (uses authenticated company). - `hires_list_company_id_mail_accounts(id)` — explicit company ID. - Company webhooks (`hires_list_webhooks` / `hires_create_webhook` / `hires_delete_webhook`) are separate from job webhooks (`hires_list_job_webhooks` / `hires_create_job_webhook` / `hires_delete_job_webhook`). ## Career site (public, no auth) `hires_list_career_jobs`, `hires_get_career_job`, `hires_submit_career_application` use `company_slug` (string) instead of API auth — they run on a separate unauthenticated client. - `hires_get_career_job(company_slug, id)` — `id` is numeric. Returns 404 for non-public jobs (draft, archived, internal). - `hires_submit_career_application` required: `company_slug`, `job_id`, `first_name`, `last_name`, `email`. Optional: `phone`, `resume` (base64 object), `linkedin_url`, `source`, `answers`. ## Evaluations & feedback - `hires_get_evaluation(id)` — `id` is an evaluation form ID, **not** application ID. Find IDs via `hires_list_application_evaluations(id=<application_id>)`. - `hires_submit_feedback(description, issue_type?)` — `issue_type`: `"missing_filter"` | `"pagination"` | `"performance"` | `"missing_field"` | `"bulk_operation"` | `"other"`. Rate-limited to 5/hour. ## Taxonomy (lookup tools) When the user asks for an entity by name, resolve via the matching `hires_list_*` first: | Need | Tool | Feeds | |---|---|---| | Rejection reason ID | `hires_list_rejection_reasons` | `reject_application.rejection_reason_id`, `batch_reject_applications.rejection_reason_id`, `disqualify_candidate.reasons[]` | | Job status label | `hires_list_statuses` | `create_job.status`, `update_job.status`, `set_job_status.status`, `list_jobs.status` | | Stage ID | `hires_list_workflow_stages(job_id=)` or `hires_get_workflow_stages(id=<workflow_id>)` | `move_application.stage_id`, `batch_move_applications.stage_id` | | User ID | `hires_list_users(search=)` | `create_interview.interviewer_ids[]`, `add_hiring_team_member.user_id`, `create_note.mention_user_ids[]` | | Mail account ID | `hires_get_user(id).default_mail_account_id` or `hires_list_user_mail_accounts` / `hires_list_company_mail_accounts` | `send_candidate_message.from_account_id` | | Tag string | `hires_list_tags` | `add_candidate_tags.tags[]`, `batch_add_tags.tags[]` | | Board identifier | `hires_list_boards` | `publish_to_job_board.boards[]` | | Email template ID | `hires_list_email_templates` | nurture `email.template_id`, `sms.template_id` | | Question ID | `hires_list_questions` | `create_form.questions[]`, `update_form_question.question_id` | | Department ID | `hires_list_departments` | `create_job.department_id` | | Question type | `hires_list_question_types` | `create_question.type` | ## Common workflows **Move candidate(s) to a specific stage on a job:** 1. `hires_list_workflow_stages(job_id=<job_id>)` → pick `stage_id`. 2. `hires_list_applications(job_id=<job_id>, candidate_id=<candidate_id>)` → get `application_id`. 3. `hires_move_application(id=<application_id>, stage_id=<stage_id>)` — or `hires_batch_move_applications` for multiple. **Schedule an interview:** 1. `hires_get_application(id=<id>)` → confirm candidate, job, current stage. 2. `hires_list_users(search="<name>")` → get interviewer user IDs. 3. Compute `start_time` and `end_time` as Unix seconds. 4. `hires_create_interview(id=<application_id>, start_time=<unix>, end_time=<unix>, interviewer_ids=[<user_id>...], location?)`. **Reject with reason and email:** 1. `hires_list_rejection_reasons` → pick `rejection_reason_id`. 2. `hires_reject_application(id=<id>, rejection_reason_id=<id>, suppress_notification=false)`. 3. To abort the auto-sent notification: `hires_cancel_all_notification_messages(candidate_id=<id>)`. **Send an email to a candidate:** 1. `hires_get_user(id=<sender_user_id>)` → read `default_mail_account_id`. 2. `hires_send_candidate_message(id=<candidate_id>, to=["candidate@..."], subject, body, from_account_id=<account_id>, scheduled_at?)`.