MCP App Store
by KeenEthics
Business & Operations
Umamy icon

Umamy

by Umamy AI

Overview

Query your Umamy recruitment pipeline directly in ChatGPT. List active hunts, look up candidates and their fit scores, read full LinkedIn message threads, access team notes, and pull pipeline analytics across your workspace. Umamy is a recruitment platform for enterprise hiring teams that helps you source, score, and engage candidates at scale. Use this app to triage your daily pipeline, brief yourself before a candidate call, draft follow-ups based on past message history, and generate weekly recaps — without leaving ChatGPT.

Tools

add_candidate_tags

ChatGPT
Purpose: Put one or more existing custom tags on a candidate. Tags the candidate already has are left as they are; every other tag on the candidate (including system tags) is preserved. NOT for: Creating a tag (use create_tag first). System tags (is_system: true) — Umamy applies those itself and the call is refused. Tags are internal labels, never visible to the candidate. Required: hunt_id + candidate_id (candidates are keyed per hunt — copy both from search_candidates / get_candidate), and tag_ids from list_tags. Returns: { candidate_id, hunt_id, changed, tags: [{ tag_id, tag_name, is_system }] } — the candidate's FULL tag list afterwards. changed: false means it already had them all: a success, do not retry. When to use: "tag <candidate> as <X>", "label these three as <X>" (one call per candidate).

add_messaging_memory_rule

ChatGPT
Purpose: Add ONE standing instruction to a hunt's memory file. Applies to every message written from now on. NOT for: Rewriting the file (use update_messaging_memory_file). Not for editing or sending a specific message. Not for changing templates (use set_hunt_details). Returns: { added, reason, rule, content }. `added: false` is a normal outcome, not an error — it means the rule is already there, the file is full, or a person removed this rule before and Umamy does not add it back. Relay the reason in your own words; do not retry and do not claim you wrote it. When to use: The user states a durable preference about HOW messages should be written: "never open with 'I hope this finds you well'", "always ask about notice period in the first follow-up", "keep it to two sentences per paragraph". The company-wide file needs an org admin. layer: "organization" works when the person you are acting for is an admin or owner of the org, and is refused otherwise — the check is server-side, on their membership, so you cannot assert it. A company-wide rule changes how every hunt at the company is written, so say that plainly and get an explicit yes before calling it, and name the file you wrote to when you report back. If it is refused, tell them an admin has to do it in Settings → Memory rather than retrying. Writing the rule: one imperative sentence, under 500 characters, about how to write — never about a candidate, and never encoding anything about a person's background, age, nationality or health.

apply_hunt_criteria_list

ChatGPT
Purpose: Bulk-add the entities on a saved criteria list (included/excluded companies or universities) into a hunt's matching criterion field. NOT for: Generic criteria writes (update_hunt_criteria). Only for the four list-backed fields below. Returns: { added: number, total_entities: number, field_slug, sync: { ok, error? } }. Like update_hunt_criteria, the route auto-syncs to AWS after a successful write — no separate sync_hunt_draft needed before get_hunt_candidate_count. When to use: "Use my standard 'top SaaS companies' list" — pair with list_personas / list browsing tools to surface saved lists.

apply_role_template

ChatGPT
Purpose: Graft a whole role archetype's criteria onto the hunt in one call — adds every criterion field from the template that the hunt is missing, each seeded with the template's default value (job-title suggestions, seniority band, tech-stack, location, …). This is how a thin new-role hunt becomes a fully-shaped Backend-Engineer / Account-Executive / … brief. NOT for: Setting a value on a field that already exists on the hunt (update_hunt_criteria). Not for browsing templates (list_role_templates) or reading one's defaults without applying (get_role_template_criteria). Not for the scorecard (update_hunt_scorecard). Behaviour: Additive and safe to repeat. Slugs already on the hunt are left untouched (never overwritten — tune those with update_hunt_criteria) and returned in skipped_existing. New rows are appended after the hunt's current criteria. Returns: { template: { slug, name }, added: [field_slug, …], skipped_existing: [field_slug, …], sync: { ok, error? } }. The route auto-syncs after a successful add, so get_hunt_candidate_count works immediately — no separate sync_hunt_draft needed. When to use: On the first turn / early setup once you've identified the role's archetype — call this before update_hunt_criteria so the role-specific fields exist (and carry sensible defaults) before you write user-specific values into them. Also when the user pivots the role ("actually make it a data engineer hunt"). Workflow: list_role_templates → pick the closest slug (or get_role_template_criteria to inspect first) → apply_role_template(slug) → get_hunt_criteria → update_hunt_criteria for anything the brief specifies → get_hunt_candidate_count.

build_entity_url

ChatGPT
Purpose: Build the canonical Umamy web deep-link for a hunt or a candidate. This is the ONLY correct way to produce an app.umamy.io link — the URL shape (domain, org slug, hunt path, candidate review-modal query params) lives here, server-side. NOT for: Fetching hunt/candidate data (use get_hunt / get_candidate). It does not read the entity — it only formats a link, so a returned URL does not imply the id exists. Returns: { url } — a ready-to-share link. For HUNT it opens the hunt page; for CANDIDATE it opens that candidate's review modal inside its hunt. When to use: - Immediately after a write action (create_hunt, duplicate_hunt, draft/send a candidate message) to link the user to the result - Whenever you want to point the user at a specific hunt or candidate in the app Required: entity_type + hunt_id. candidate_id is required when entity_type is CANDIDATE. Never hand-write an app URL yourself — always get it from this tool.

correct_candidate_signal

ChatGPT
Purpose: Replace one signal's VALUE with what the user knows to be true — e.g. a "passed on this role" signal whose reason was compensation but the candidate told the recruiter on the phone it was timing. Writes a new signal dated today (kind: recruiter, overrides = the original), marks the original overridden, stamps who did it, and records it in the activity feed. NOT for: Types whose recruiter_edit is dismiss_only (open_to_opportunities, open_to_relocation, not_open_to_relocation) — a "correction" there could only re-assert the same thing, so the call is refused (400); use dismiss_candidate_signal. Not for stale signals (their weight already fades). Never on your own initiative. Required: hunt_id + candidate_id, signal_id from list_candidate_signals, and note — what the user told you (required: it is the correction's evidence). value — only the fields the type's value_fields list (check list_signal_types), e.g. { "reason": "not_now" } with a reason from its reason_values. Returns: { ok: true, signal } — the NEW signal. 400 = dismiss-only type or a value the type does not accept. 409 = the original is no longer active. When to use: "it wasn't the salary, they said the timing is wrong", "change the reason to not now — they told me on the phone".

create_candidate_note

ChatGPT
Purpose: Leave an internal team note on a candidate — private commentary among the hiring team, never visible to the candidate. NOT for: Messaging the candidate (that's a LinkedIn message — use the messaging tools). Not for editing an existing note (use update_candidate_note). Returns: { note } — the created note with id, hunt_id, candidate_id, content, author_id, created_at. Keep the id if you may need to update or delete it later. When to use: User says "add a note to candidate X", "note that [name] prefers remote", "log this feedback on the candidate". Workflow: list_organizations → search/list candidates to get hunt_id + candidate_id → create_candidate_note(org_id, hunt_id, candidate_id, content). Tagging a teammate: call list_org_members first, then put the literal @<profile_id> inside content — e.g. "@11111111-2222-4333-8444-555555555555 can you screen this one?". The app renders it as @Display Name and emails that person. Copy the id verbatim from list_org_members; a name or an invented id is rejected with a 400 naming the bad ids. Mentions are optional — plain text is fine.

create_hunt

ChatGPT
Purpose: Create a new draft hunt in an organization and return it. Seeds the generic baseline criteria (the new-role archetype) and syncs it to the matching engine, so it's ready to set up immediately. NOT for: Editing an existing hunt's identity (set_hunt_details) or criteria (update_hunt_criteria). Not for duplicating an existing hunt (duplicate_hunt). Not for launching — going live stays a human action. Returns: { id, name, role_name, status: "draft", synced, url }. Use id for every follow-up setup call, and give the user the `url` — it is the hunt's page, already built, so you do not need build_entity_url after this. When to use: "Create a hunt for <role>", "start a new search for <role>". Right after, name it precisely with set_hunt_details, then shape it with update_hunt_criteria. Required: organization_id (must be one the user belongs to). name / role_name are optional labels — omit them and the hunt starts from a generic default name, which you then set with set_hunt_details. `setup_prompt` — pass it whenever you have a brief. It is the hiring brief (role, seniority, location, must-haves, a job-post URL or its text, comp), and by default it makes the hunt set itself up: the setup agent is handed it the moment the hunt exists and works through details, criteria and the scorecard on its own, in the hunt's own chat thread, before anyone opens a browser. role_name is derived from it when you don't supply one. The response says whether the agent started (setup_agent_started); if it could not, the brief stays on the hunt and the in-app chat replays it when a human opens the hunt, so the brief is never lost either way. So prefer ONE call with a brief over ten configuring calls. With setup_prompt you usually do not need set_hunt_details / update_hunt_criteria / update_hunt_scorecard at all — the agent does that work. Reach for those only to change something after the fact, or when the user gave you no brief to pass on.

create_tag

ChatGPT
Purpose: Create a new custom candidate tag for the organization. NOT for: Putting a tag on a candidate (use add_candidate_tags — create the tag first if it does not exist). Not for system tags, which only Umamy creates. Returns: { tag: { tag_id, tag_name, is_system } }. A 409 means a tag with that name already exists (or a system tag has that name) — call list_tags and reuse its id instead of retrying. When to use: - "create a tag called <X>", "add a <X> tag we can use" - The user wants to tag candidates with a label that list_tags shows does not exist yet — confirm the name with them first

delete_agent

ChatGPT
Purpose: Delete a saved agent and stop its schedule. Past runs and their transcripts are kept. NOT for: Pausing an agent temporarily — use update_agent with enabled: false (deletion is permanent). Returns: { ok: true, runs_retained: true }. When to use: "delete my digest agent", "remove the nightly agent for good". Required: organization_id, agent_id.

delete_candidate_note

ChatGPT
Purpose: Permanently delete a team note from a candidate. Irreversible. NOT for: Editing a note's text (use update_candidate_note). Not for deleting candidates or messages. Returns: { success: true }. When to use: User says "delete that note", "remove my note on X". Confirm with the user before calling — the note cannot be recovered. Authorization: Only the note's author can delete it — deleting someone else's note returns a 403. Get the note_id from list_notes / get_candidate_notes.

delete_tag

ChatGPT
Purpose: Permanently delete a custom tag AND remove it from every candidate in the organization that carries it. Irreversible — recreating a tag with the same name does not put it back on anyone. NOT for: Taking a tag off one candidate (use remove_candidate_tags — the tag keeps existing). System tags cannot be deleted (403). Returns: { success: true }. When to use: Only when the user explicitly asks to delete the tag itself ("delete the <X> tag", "we don't use <X> any more"). Tell them it will be removed from every candidate first and get an explicit yes.

dismiss_candidate_signal

ChatGPT
Purpose: Mark one signal as WRONG ("Not right") for this organization. The row is kept with status dismissed, stamped with who did it, and recorded in the candidate's activity feed. It stops counting anywhere. NOT for: A signal that is merely OLD — age is already handled by its weight; dismiss only what the candidate never meant. Not for changing a value (correct_candidate_signal). Never on your own initiative: only when the user says the signal is wrong. Required: hunt_id + candidate_id (from search_candidates / get_candidate), signal_id from list_candidate_signals. note — the user's reason, in their words, when they gave one. Returns: { ok: true, signal } with status: "dismissed". 409 = the signal is no longer active (already dismissed or corrected): re-read, do not retry. 404 = not this organization's candidate or signal. When to use: "that's wrong, they never said they'd relocate", "remove the open-to-opportunities signal, it was someone else".

duplicate_hunt

ChatGPT
Purpose: Create a new DRAFT hunt by copying an existing one's criteria, scorecard, outreach configuration, sourcing settings, and language. The new hunt is owned by the caller and starts empty (no candidates / messages / notes). NOT for: Editing an existing hunt (configuration changes happen in the Umamy dashboard). Not for creating a hunt from scratch (use the dashboard's setup wizard). Candidates, messages, notes, the linked LinkedIn account, and ATS connection are NEVER copied — those are reassigned manually on the new hunt. Returns: The newly created hunt — { id, name, status, role_name, created_at, updated_at }. status is always "draft" regardless of the source hunt's status. When to use: - "Spin up a copy of the Senior Engineer hunt for the EU team" - "Duplicate hunt X but skip the outreach templates" - "Clone this hunt as a new draft so I can iterate on it" Workflow: list_organizations → list_hunts(org_id) → duplicate_hunt(org_id, source_hunt_id, name). Defaults: All copy_* flags default to true when omitted; pass false to skip a section. add_me_as_owner defaults to true and is a no-op if the caller is already an owner of the source hunt.

generate_scorecard_criterion

ChatGPT
Purpose: Propose 2–3 wordings for one new scorecard criterion that the user can pick from. NOT for: Replacing the whole scorecard (regenerate_hunt_scorecard). Not for persisting — it returns suggestions; call update_hunt_scorecard to commit a chosen one. Returns: A small array of candidate criterion drafts. Workflow: Server-side already follows get_authoring_guide("scorecard_criterion") rules. If the user is asking you to draft the wording before committing, fetch the guide first for the same rules. When to use: "Add a criterion about X" — gather options, let the user choose, then call update_hunt_scorecard with the chosen wording + a sensible rank.

get_agent

ChatGPT
Purpose: Fetch one agent's full record — name, prompt, schedule, enabled state, last-run status. NOT for: Listing all agents (list_agents). Triggering a run (run_agent). Returns: The agent { agent_id, name, prompt, schedule, enabled, last_run_at, last_run_status, run_count, ... }. When to use: "show me the digest agent", "what does my nightly agent do?", before editing so you can preserve the existing prompt/schedule. Required: organization_id, agent_id.

get_authoring_guide

ChatGPT
Purpose: Fetch the in-house authoring rules + canonical variable list for one kind of artifact you're about to write (outreach message, connection note, scorecard criterion, requirements filter, sourcing setup). The same rules drive Umamy's built-in AI generators — calling this keeps your output consistent with what the user already sees elsewhere in the product. NOT for: Reading the hunt's values (use get_hunt_criteria / get_hunt_scorecard / get_hunt_configuration). This tool returns editorial guidance, not data. Returns: { kind, guide: string } — a markdown blob to follow when drafting. For message-template kinds the markdown includes a table of the only allowed {{ placeholders }}; using anything else will be rejected by the write tool. When to use: Before any of these writes, especially the first time per conversation. Cheap to call. - Before set_hunt_message_sequence (intro / follow-ups / approve / reject) → kind: "outreach_message". - Before set_hunt_message_sequence with connection_note_template → kind: "connection_note" (smaller placeholder set, 200-char limit). - Before update_hunt_scorecard / generate_scorecard_criterion / regenerate_hunt_scorecard → kind: "scorecard_criterion". - Before update_hunt_criteria / apply_hunt_criteria_list → kind: "filters". - Before set_hunt_sourcing → kind: "sourcing".

get_candidate

ChatGPT
Purpose: Full record for one candidate — profile (name, headline, experience, education), connection status, scorecard answers, score, and the entire message thread. NOT for: Just the messages (use get_candidate_messages — same data, leaner payload). Not for notes (use get_candidate_notes). Returns: A single candidate object including messages array. Use this when you want everything in one go. When to use: User asks "tell me about candidate X", "show me [name]'s profile", "what does the team know about this candidate?". Required: Both organization_id AND hunt_id — candidates are keyed by (candidate_id, hunt_id) on the backend; the same person may appear in multiple hunts as separate records. Get hunt_id from list_candidates (each candidate is tagged with the hunt it belongs to). Workflow: list_organizations → list_candidates(org_id) → get_candidate(org_id, hunt_id, candidate_id)

get_candidate_messages

ChatGPT
Purpose: The full LinkedIn message thread with one candidate — every message sent and received, with sender, body, timestamp, and delivery status. NOT for: Internal team notes about the candidate (use get_candidate_notes). Not for the profile / scorecard (use get_candidate). Returns: Array of messages. Outdated/superseded messages (status MESSAGE_OUTDATED) are filtered out. When to use: User asks "what did we say to candidate X?", "did [name] reply?", "show me the conversation with this person". Required: organization_id AND hunt_id. If the user just gives you a candidate name, find them with list_candidates first to grab the hunt_id tag. Workflow: list_organizations → list_candidates(org_id) → get_candidate_messages(org_id, hunt_id, candidate_id)

get_candidate_notes

ChatGPT
Purpose: Internal notes left by team members on a candidate (free-text, ordered chronologically). Notes are NOT visible to the candidate — they're private commentary among the hiring team. NOT for: LinkedIn messages with the candidate (use get_candidate_messages). Notes and messages are completely separate systems. Returns: Array of { id, hunt_id, candidate_id, content, author_id, created_at }, oldest first. When to use: User asks "what notes have we left on X?", "what did the team think of this candidate?", "any feedback on [name]?". Note on hunt_id: Optional. If the same person is in multiple hunts, omitting hunt_id returns notes across all of them within the org; pass hunt_id to scope to one. Org-wide authorization is always enforced. Workflow: list_organizations → list_candidates(org_id) → get_candidate_notes(org_id, candidate_id, [hunt_id])

get_file_content

ChatGPT
Purpose: Read the text of one uploaded file — what a PDF, Word document, or scanned image actually says. NOT for: Downloading the original bytes (this returns text only). Not for files you have not been given an id for — never construct or guess a file_id. Returns: { file_id, filename, content, offset, returned_chars, total_chars, has_more, next_offset, extraction_truncated }. Reading long documents: content comes back in pages. When has_more is true, call again with offset set to next_offset — do not answer from the first page as though it were the whole document. extraction_truncated: true means the document was longer than we store and its tail is gone for good; say so if it matters. When the file is not ready: you get an error carrying extraction_status instead of content. extracting means try again shortly; failed or unsupported means the text does not exist — tell the user and ask them to paste it, rather than retrying in a loop. When to use: After list_files_for_hunt, or when a conversation names a file_id, and you need what the document says. Workflow: list_files_for_hunt(org_id, hunt_id) → get_file_content(org_id, file_id) → [if has_more] get_file_content(org_id, file_id, offset=next_offset) Cross-org: One org per call. A file_id from one organization is not readable from another.

get_hunt

ChatGPT
Purpose: Full record for one hunt — name, status, criteria, owner, timestamps, high-level config flags. NOT for: Pipeline funnel numbers (use get_hunt_metrics_distribution with metric=pipeline_status). Not for the scorecard / outreach config (use get_hunt_configuration). Not for candidates (use list_candidates with hunt_id). Returns: A single hunt object. When to use: User asks "tell me about hunt X", "what's the status of the Senior Engineer role?". Or you have a hunt_id from list_hunts and need its details. Workflow: list_organizations → list_hunts(org_id) → get_hunt(org_id, hunt_id)

get_hunt_analytics_summary

ChatGPT
Purpose: High-level analytics rollup — totals (and optional deltas vs the previous period) for several timeseries metrics in ONE call. Use this when the user asks "what's improving / declining?", "how did we do this month?", "compare these hunts" — instead of calling get_hunt_metrics_timeseries 4–8 times yourself. NOT for: A single metric over time (use get_hunt_metrics_timeseries). Not for funnel distributions or fit-score histograms (use get_hunt_metrics_distribution). Returns: { ok, date_range, previous_date_range?, per_metric: { [metric]: { total, previous_total?, delta?, delta_pct? } }, partial_errors? }. delta_pct is null when the previous period had zero events (no defined growth rate). partial_errors lists metrics that failed if any; the others still come back. Default metrics (when metrics is omitted): the funnel — candidates_sourced, connections_sent, messages_received, candidates_shortlisted, candidates_hired. Pass your own comma-separated list to override. Available metrics: Same slugs as get_hunt_metrics_timeseries (candidates_sourced, candidates_enriched, connections_sent, connections_accepted, messages_sent, messages_received, positive_responses, negative_responses, neutral_responses, candidates_shortlisted, candidates_hired, candidates_rejected, candidates_not_interested). When to use: - "What's our performance this month vs last month?", "anything declining?", "how did the Backend hunt do over the last 30 days?" - Any time you'd otherwise call get_hunt_metrics_timeseries repeatedly with the same window Workflow: - Org rollup: list_organizations → get_hunt_analytics_summary(org_id, date_from, date_to, compare_to_previous_period: true) - One hunt: list_organizations → list_hunts(org_id) → get_hunt_analytics_summary(org_id, date_from, date_to, hunt_id) Dates are absolute ISO YYYY-MM-DD. Resolve relative offsets like "the last 30 days" to a real start/end date before calling.

get_hunt_candidate_count

ChatGPT
Purpose: Get the estimated candidate pool size for the hunt's current criteria — the feedback signal used to suggest loosening (count = 0) or tightening (count in the thousands) filters. NOT for: Actually listing those candidates (use preview_hunt_candidates for a sample, or list_candidates after launch). Returns: { count: number } — a quick estimate from the search backend. Requires the hunt to have a backend_job_id; if you get a "must sync first" error, call sync_hunt_draft and retry. When to use: After every meaningful criteria edit during requirements setup, so the user sees the pool changing in real time. Workflow: update_hunt_criteria → sync_hunt_draft (if not synced) → get_hunt_candidate_count.

get_hunt_configuration

ChatGPT
Purpose: Inspect how a hunt is configured — the scoring rubric (scorecard), outreach message templates, and automation rules. NOT for: Pipeline numbers (use get_hunt_metrics_distribution with metric=pipeline_status). Not for the candidate list. Not for editing — this is read-only; configuration changes happen in the Umamy dashboard. Returns: Nested object with scorecard (criteria + weights), outreach (message templates / sequences), and automation (auto-actions on connection events). When to use: User asks "how is hunt X scoring candidates?", "what messages are being sent on the Senior Engineer hunt?", "show me the outreach setup", or wants to debug why candidates are scored a certain way. Workflow: list_organizations → list_hunts(org_id) → get_hunt_configuration(org_id, hunt_id)

get_hunt_criteria

ChatGPT
Purpose: Read all candidate-filter fields on a hunt — the rows on hunt_criteria joined with their field templates so you know the slug, the human name, the type (text / select / multiselect / tags / entity-search / location / structured_object), whether it's required, current value, and option set for select fields. NOT for: The scoring rubric (use get_hunt_scorecard). Not for the outreach config or sourcing settings (use get_hunt_configuration). Not for resolving raw text into entity IDs — use search_hunt_entities for that. Returns: { criteria: [{ field_slug, field_name, field_type, is_required, field_value?, field_value_string_array?, field_value_search_criteria_array?, field_value_location?, helper_text, theme, display_order, entity_search_type }] } — ordered by display_order. Reading the right value column for the field's type is your job (text→field_value, select/multiselect/tags→field_value_string_array, entity-search→field_value_search_criteria_array, location→field_value_location, structured_object→field_value as JSON string). When to use: - Starting a hunt-setup conversation — first call after getting the hunt id. - "What's left to fill in?" — work the is_required-and-empty rows first. - Before writing values via update_hunt_criteria — to know which columns + slug to send. Workflow: get_hunt → get_hunt_criteria → update_hunt_criteria (loop with search_hunt_entities / search_hunt_locations for entity/location fields).

get_hunt_metrics_distribution

ChatGPT
Purpose: Bucket distribution for one hunt-analytics metric. Powers histograms (score, years-of-experience), pie charts (sentiment, tags), and the kanban-style pipeline funnel. Forwards to AWS gateway /metrics_distribution. NOT for: "Over time" trends (use get_hunt_metrics_timeseries). Not for fetching individual candidates (use list_candidates). Returns: { ok, metric, date_range, buckets: [{ range, total, per_job }], jobs, meta: { total_candidates, total_jobs, returned_jobs } }. Each bucket is a category; per_job breaks the count down by hunt. Available metrics: - pipeline_status — candidate funnel (INGESTION → CONNECTION_SENT → CONNECTED → CANDIDATE_APPROVED → REJECTED_MERGED…). Use this for "candidates by status" / "what's the funnel" questions. - score — fit-score distribution in 10 buckets (0-10, 11-20, …, 91-100) - yoe — years-of-experience distribution (<1y, 1-2y, 3-5y, 6-10y, 10+y) - sentiment — POSITIVE / NEGATIVE / NEUTRAL counts of candidate replies - tags — tag counts (sorted descending; respects top_n) When to use: - "What's the funnel on hunt X?", "score distribution across my org", "sentiment of replies on the Senior Engineer hunt", "top tags on candidates" Workflow: - Org-wide distribution: list_organizations → get_hunt_metrics_distribution(org_id, metric) - Per-hunt funnel: list_organizations → list_hunts(org_id) → get_hunt_metrics_distribution(org_id, "pipeline_status", hunt_id) - Cross-org: list_organizations → loop through every id, calling this tool per org → combine yourself `top_n`: Only meaningful for tags and across-hunt distributions where there are many jobs — caps the per_job breakdown and merges the rest into _other.

get_hunt_metrics_overview

ChatGPT
Purpose: How big each hunt's candidate market is, how many candidates are on the hunt, and how fast each side of the conversation replies. One row per hunt: tam, discovered, average candidate response time, average recruiter response time, and the share of recruiter replies inside 24h (the SLA). NOT for: Counts over time (use get_hunt_metrics_timeseries), fit-score or pipeline histograms (use get_hunt_metrics_distribution), or multi-metric funnel deltas (use get_hunt_analytics_summary). Returns: { ok, date_range, hunts: [{ job_id, job_name, tam, discovered, candidate_response_time_ms, candidate_response_sample_size, recruiter_response_time_ms, recruiter_response_sample_size, recruiter_within_24h_pct }], org }. Durations are in milliseconds — convert before reporting them (86400000 ms = 1 day). Reading it honestly — four things that will otherwise mislead you: - `null` never means zero. tam: null means this hunt's candidate pool has not been priced yet; a null duration with *_sample_size: 0 means nobody has replied yet. Say "not available", never "0" or "instant". An unpriced hunt is usually priced by a later read, so if tam matters to the answer, say it is not available yet rather than that the hunt has no market. - Always quote the sample size alongside a duration. A 4-hour average over 3 replies is not a finding. - `tam` and `discovered` are NOT a ratio. Never divide them. tam is how many people the sourcing provider matches for the criteria in force RIGHT NOW. discovered is how many candidates are on the hunt, accumulated over its whole life under ANY criteria it has ever had. A percentage of one over the other is meaningless — the API used to return one and it was removed for exactly this reason. Report them as two separate numbers. - `org` has no TAM, deliberately. Two hunts can target the same market, so summing per-hunt tam would count the same people twice. org.discovered does sum, because a candidate belongs to one hunt. Time window: date_from/date_to bound the two response times only. tam is the market as it stands now and discovered is a lifetime count, so neither takes the window — describe them as "all time", not as belonging to it. Excluded from the response times: messages that predate the hunt. Conversations a recruiter already had with a candidate on LinkedIn are ingested but not measured, so these numbers reflect Umamy outreach only. When to use: - "How big is the market for this role?", "how much of the pool have we covered?", "why is this hunt slower — is the market just smaller?" - "How fast do candidates reply?", "are we hitting the 24-hour response SLA?", "which hunt has the worst recruiter response time?" - Comparing hunts across very different market sizes, where raw counts mislead Workflow: - All hunts: list_organizations → get_hunt_metrics_overview(org_id) - One hunt: list_organizations → list_hunts(org_id) → get_hunt_metrics_overview(org_id, hunt_id) Dates are absolute ISO YYYY-MM-DD. Resolve "the last 30 days" to real dates before calling.

get_hunt_metrics_timeseries

ChatGPT
Purpose: Time-series counter for one hunt-analytics metric over time. Powers trend charts ("messages sent per week", "connections accepted over the last 30 days"). Forwards to AWS gateway /metrics_timeseries. NOT for: Bucket distributions like score histograms or pipeline funnels (use get_hunt_metrics_distribution). Not for organization-level metrics like team members or billing. Returns: { ok, metric, date_range: { from, to }, interval, total, series: [{ period, total, ...per_job }], series_keys }. Each series entry is a time bucket; per_job keys break the count down by hunt. Available metrics: - candidates_sourced — new candidates added to the funnel (INSERT events) - candidates_enriched — candidates that passed enrichment - connections_sent — LinkedIn connection requests sent - connections_accepted — connection requests accepted by the candidate - messages_sent — outbound messages sent to candidates - messages_received — inbound replies from candidates - positive_responses — replies with POSITIVE sentiment - negative_responses — replies with NEGATIVE sentiment - neutral_responses — replies with NEUTRAL sentiment - candidates_shortlisted — candidates moved to CANDIDATE_APPROVED - candidates_rejected — CONNECTION_REJECTED_, CANDIDATE_REJECTED - `candidates_not_interested` — CANDIDATE_NOT_INTERESTED When to use: - "How many connections were sent this week?", "trend of replies over the last month", "show messages sent per day on hunt X" - Any "over time" question Workflow: - Org-wide trend: list_organizations → get_hunt_metrics_timeseries(org_id, metric) - Per-hunt trend: list_organizations → list_hunts(org_id) → get_hunt_metrics_timeseries(org_id, metric, hunt_id) - Cross-org trend: list_organizations → loop through every id, calling this tool per org → combine yourself (note: response shapes don't merge cleanly; per-org summaries are usually what users want) Date filters: date_from / date_to accept ISO 8601 (2026-04-01) or relative (-30d, -1y). interval is day | week | month and defaults sensibly based on range. Computed rates: Rates like "connection acceptance rate" are not stored — derive them by calling two timeseries (connections_accepted and connections_sent) and dividing.

get_hunt_scorecard

ChatGPT
Purpose: Read the evaluation scorecard for a hunt — the list of { id, text, is_mandatory, rank } items each candidate will be scored against. NOT for: Candidate-filter criteria (get_hunt_criteria). Not for individual candidate scores (use the candidate tools). Returns: { criteria: [{ id, text, is_mandatory, rank }] } — empty list if no scorecard has been built yet. When to use: Before update_hunt_scorecard (read-modify-write), or to check whether the scorecard meets the "≥3 items" bar before treating the scorecard step as done. Workflow: get_hunt_criteria (hiring brief) → get_hunt_scorecard → update_hunt_scorecard (or regenerate_hunt_scorecard / generate_scorecard_criterion for AI-assisted drafting).

get_hunt_setup_checklist

ChatGPT
Purpose: Backend-computed "what's left to set up" checklist for one hunt. Mirrors the hunt-edit stepper and returns only the items that are relevant (LinkedIn / message-sequence items appear only for outbound hunts; the ATS item appears only when the org has an active ATS connection). Each item is server-evaluated as complete from the authoritative state (Supabase hunt_criteria / hunts.persona_id + AWS Job sourcing / scorecard / outreach / ATS). NOT for: How the hunt is configured today (use get_hunt_configuration — that returns the actual scorecard rubric, outreach templates, automation rules). Not for editing — this is read-only; the matching write tools (set_hunt_sourcing, update_hunt_criteria, update_hunt_scorecard, set_hunt_persona, set_hunt_message_sequence, set_hunt_ats_routing) live in the hunt-setup toolset. Not for pipeline funnel numbers (use get_hunt_metrics_distribution). Returns: { items: [{ key, step: "sourcing"|"requirements"|"scorecard"|"outreach", order, description, instructions, complete }], finished }. key is the stable item identifier (setup_sourcing, clarify_key_requirements, establish_scorecard, connect_linkedin_account, setup_message_sequence, connect_ats) — clients use it to localize description. order is contiguous after irrelevant items are filtered out. instructions is server-owned English guidance for the AI on how to complete that item (it points at the matching get_authoring_guide(kind) + the write tool to use); translate when you speak to the user, per the "match the user's language" rule. finished is true only when every returned item is complete. When to use: - User asks "what's left to set up hunt X?", "is hunt X ready to launch?", "why can't I launch?", "what's the next step on the SDR hunt?" - Start of a hunt-setup conversation, to plan the walkthrough - Before claiming a hunt is ready to launch — never say it's ready unless finished === true Workflow: get_hunt_setup_checklist(org_id, hunt_id) → for each complete: false item in order, follow its instructions (which calls out get_authoring_guide(kind) + the right write tool) → re-call get_hunt_setup_checklist after meaningful writes to surface progress.

get_organization_metrics

ChatGPT
Purpose: Hunt totals by lifecycle status — { total_hunts, live_hunts, draft_hunts, paused_hunts, completed_hunts }. Counts hunts themselves, not candidates inside them. NOT for: Per-hunt candidate funnel numbers (use get_hunt_metrics_distribution with metric=pipeline_status). Not for activity analytics like connections_sent or messages_received (use get_hunt_metrics_timeseries). Returns: { total_hunts, live_hunts, draft_hunts, paused_hunts, completed_hunts } for one org. When to use: User asks "how many hunts do I have?", "how many active campaigns?", "give me an overview of my workspace". Workflow: list_organizations → get_organization_metrics(org_id) Cross-org: Always returns counts for one org. To answer "how many hunts across all my orgs?", loop: list_organizations → for each id, get_organization_metrics(id) → sum yourself.

list_agents

ChatGPT
Purpose: List an organization's saved agents with their schedule and last-run status. NOT for: One agent's full prompt + recent runs (use get_agent). A run's transcript/output (use get_agent then the run tools / UI). Returns: { items: [{ agent_id, name, schedule, enabled, last_run_at, last_run_status, ... }] }. When to use: "what agents do I have?", "list my agents", "is my digest agent still running?". Required: organization_id.

list_ats_connections

ChatGPT
Purpose: List the org's active ATS connections (Workable, Greenhouse, Lever, Ashby, …). Use this to decide whether the "Connect ATS" item on the setup checklist is relevant and which connection to map to the hunt. NOT for: Connecting a new ATS — that's a browser OAuth flow handled by the in-app AtsConnectionModal (surfaced via the local "Connect ATS" chat button). Returns: { ats_connections: [...] } — pick ats_connection_pk to then call list_ats_jobs, then map via update_hunt_configuration. When to use: Anywhere ATS is in scope — before mapping the hunt to an ATS job, before suggesting the user connect a new ATS.

list_ats_jobs

ChatGPT
Purpose: List jobs available on one ATS connection so the assistant can pick the right ats_job_id to map to the hunt. NOT for: Listing connections (list_ats_connections first). Not for writing the mapping — pair with update_hunt_configuration (ats_connection_pk + ats_job_id). Returns: { ats_jobs: [...] }. When to use: Right before update_hunt_configuration in inbound-with-ATS or send-approved-to-ATS flows.

list_candidates

ChatGPT
Purpose: List candidates (profiles surfaced into a hunt's pipeline) for one organization. Each candidate has a profile, connection status, score, and message thread. NOT for: Listing hunts (use list_hunts). Not for a single candidate's full record (use get_candidate). Not for messages or notes (use list_messages / list_notes or the candidate-scoped variants). Returns: Array of candidate objects with profile fields, connection_status, score, scorecard answers, message thread, and a hunt_id tag (so you can resolve back to the hunt for follow-up calls). When to use: - User asks "show me candidates", "who's connected on hunt X?", "list everyone who replied" - Need to find a candidate_id before drilling into get_candidate / get_candidate_messages / get_candidate_notes Workflow: list_organizations → list_candidates(org_id, [hunt_id], [status]) → get_candidate(org_id, hunt_id, candidate_id) / get_candidate_messages(...) / get_candidate_notes(...) Filters: - hunt_id — scope to a single hunt's pipeline - status — filter by connection status (e.g. CONNECTED, PENDING, REPLIED, REJECTED) Pagination: Set limit (and pass cursor from a previous response's next_cursor) to page through one hunt's candidates — only works when hunt_id is set. Without hunt_id the v1 endpoint fans out across every hunt in the org and returns a single merged page; use limit to cap. Cross-org: Always returns candidates for one org. To search across orgs, loop: list_organizations → for each id, list_candidates(id) → combine yourself.

list_files_for_hunt

ChatGPT
Purpose: List the files people have uploaded to a hunt or to one agent conversation — job descriptions, briefs, scoring rubrics, candidate lists. NOT for: Files an autonomous agent produced during a run (use get_agent / the agent's run detail). Not for candidate CVs synced from an ATS (those live on the candidate record). Returns: { items, cursor }. Each item has file_id, filename, mimetype, size_bytes, extraction_status, created_at, and hunt_id / channel_conversation_id when the file is attached to one. Newest first. It never carries the file's text — read that with get_file_content. An empty `items` with a `cursor` is not "no files": visibility is applied after the page is read, so a page can come back empty (or short) while more files match — call again with the cursor. Only an empty items and NO cursor means nothing has been uploaded. Reading the status: only ready means the text is available. extracting means it is still being read (try again shortly); failed and unsupported mean there is no text and never will be — say so rather than guessing what the document said. When to use: The user refers to something they "sent", "shared" or "uploaded earlier" that is not in the visible conversation; or before asking someone to re-send a document you might already have. Workflow: list_organizations → list_files_for_hunt(org_id) for everything, or → list_hunts(org_id) → list_files_for_hunt(org_id, hunt_id) to narrow → get_file_content(org_id, file_id) Filters — both optional, and omitting both is the org-wide list. organization_id alone returns every file in that organisation you are allowed to see, newest first. So "all the files I have uploaded" is one call; do not refuse it or ask the user to pick a hunt first. hunt_id narrows to everything uploaded for that role, including by teammates. conversation_id narrows to one chat thread. Given both, hunt_id wins. On an unfiltered list, each item's own hunt_id / channel_conversation_id tells you where it came from — say so, since the user cannot otherwise tell one thread's files from another's. Cross-org: One org per call. To scan across orgs, loop: list_organizations → for each id, list_files_for_hunt(id).

list_hunts

ChatGPT
Purpose: List hunts (recruitment campaigns) for one organization. A hunt is a single open role being filled — it has a name, target candidate criteria, an outreach configuration, and a candidate pipeline. NOT for: Listing candidates (use list_candidates). Not for hunt analytics like funnel counts or activity over time (use get_hunt_metrics_distribution / get_hunt_metrics_timeseries). Returns: Array of hunts with id, name, status (DRAFT/LIVE/PAUSED/COMPLETED), creation timestamps. Use id with the per-hunt tools below. When to use: - User asks "show my hunts", "what roles am I hunting for?", "list active campaigns" - Need a hunt id before drilling into candidates or analytics for a specific role Workflow: list_organizations → list_hunts(org_id) → get_hunt / get_hunt_configuration / list_candidates(hunt_id) / get_hunt_metrics_ Cross-org:* Always returns hunts for one org. To list across all orgs, loop: list_organizations → for each id, call list_hunts(id) in parallel → merge yourself.

list_messages

ChatGPT
Purpose: Flat list of LinkedIn messages across one organization — useful for activity reports, "what got sent today?", or recent reply scans without pulling full candidate objects. NOT for: A single candidate's thread (use get_candidate_messages — same data, indexed under one person). Not for internal notes (use list_notes). Returns: Flat array of messages, each tagged with its hunt_id (so you can resolve back to the hunt). Outdated messages are filtered out. When to use: User asks "how many messages went out this week?", "show me recent replies", "list outbound messages on hunt X". Workflow: list_organizations → list_messages(org_id, [hunt_id], [candidate_id], [status]) Filters: - hunt_id — scope to one hunt - candidate_id — scope to one candidate (REQUIRES hunt_id) - status — e.g. MESSAGE_SENT, MESSAGE_RECEIVED Cross-org: Always returns messages for one org. To scan across orgs, loop: list_organizations → for each id, list_messages(id) → combine yourself.

list_messaging_memory_versions

ChatGPT
Purpose: The save history of one memory file, newest first. Returns: { versions } — each with versionId, lastModified and whether it is the current one. When to use: "when did that rule appear?", "what did this look like last week?", "undo what changed yesterday" (list first, then restore_messaging_memory_version).

list_notes

ChatGPT
Purpose: Flat list of internal team notes across one organization. Useful for activity scans like "what did the team comment on this week?". NOT for: Notes on a single candidate (use get_candidate_notes — same data, scoped). Not for LinkedIn messages (use list_messages). Returns: Flat array of notes ordered most-recent first, each with hunt_id, candidate_id, content, author_id, created_at. When to use: User asks "show me recent team notes", "what comments has the team left on this hunt?", "list notes across my org". Workflow: list_organizations → list_notes(org_id, [hunt_id], [candidate_id]) Filters: hunt_id and/or candidate_id to scope. Cross-org: Always returns notes for one org. To scan across orgs, loop: list_organizations → for each id, list_notes(id) → combine yourself.

list_org_members

ChatGPT
Purpose: The teammates in one organization, with the profile_id you need to @mention someone in a candidate note. NOT for: Candidates (use search_candidates — candidates are not members). Not for inviting, removing, or changing a teammate's role. Emails and invitation links are deliberately not returned. Returns: { data: [{ profile_id, display_name, role }] } — active members only. A pending invite has no profile yet and cannot be mentioned, so it is omitted. When to use: - Before create_candidate_note / update_candidate_note when the user wants a teammate tagged ("add a note and tag Sarah", "flag this for the hiring manager") - User asks "who's on my team?" / "who else is in this org?" How a mention works: put the literal @<profile_id> inside the note content — e.g. "Strong fit, @11111111-2222-4333-8444-555555555555 can you screen?". The app renders it as @Display Name and emails that person. Match the user's words to a display_name here and copy its profile_id verbatim; a name or an invented id is rejected with a 400. If nobody matches, ask the user rather than guessing.

list_organizations

ChatGPT
Purpose: List every Umamy organization (workspace) the signed-in user is a member of. This is your entry point — almost every other tool needs an organization_id, and this is how you discover them. NOT for: Listing hunts inside an org (use list_hunts). Not for fetching teammates, billing, or org settings. Returns: Array of { id, name, role }. Pass the id as organization_id to other tools. When to use: - First call in almost any conversation that touches Umamy data — you can't address most resources without an org id - User asks "what organizations / companies do I have access to?" - Disambiguating which org owns a hunt or candidate before drilling deeper Workflow patterns: - Single-org: list_organizations → pick one id → call list_hunts / list_candidates / get_hunt_metrics_ with that id - Cross-org analysis: list_organizations → loop through every id, calling tools per org in parallel, combine results yourself - Drill into a hunt: list_organizations → list_hunts(org_id) → get_hunt(org_id, hunt_id) - Drill into a candidate: list_organizations → list_candidates(org_id) → get_candidate(org_id, candidate's hunt_id, candidate_id) Why no automatic fan-out:* Different tools have different response shapes; cross-org merging is the model's job, not the server's. Looping is explicit, parallelisable, and shows your work.

list_personas

ChatGPT
Purpose: List the organization's outreach personas — each ties a connected LinkedIn account to a tone-of-voice config that hunts can reuse for outbound messaging. NOT for: Connecting a new LinkedIn account — that's a browser OAuth flow handled by the in-app LinkedInConnectionDialog (the chat surfaces a "Connect LinkedIn" button locally; no MCP tool drives it). Returns: { personas: [...] } — pick a persona_id to associate with the hunt's outreach via the outreach UI (the assistant can navigate the user to the outreach step and recommend one of these). When to use: "Which LinkedIn accounts can this hunt send from?" / before suggesting the user assign a persona during outreach setup.

list_signal_types

ChatGPT
Purpose: The catalog of candidate signal types — what each one means, how fast it fades, which value fields it carries, and whether a recruiter may correct it or only dismiss it. Identical for every organization. NOT for: A candidate's actual signals (use list_candidate_signals). Returns: { signal_types: [{ key, label, group, factor, direction, exclusive_with, value_fields, reason_values, freshness: { class, full_weight_days, half_weight_days, faded_after_days }, base_confidence, recruiter_edit: "dismiss_only" | "edit_value", i18n }] }. When to use: - Before correct_candidate_signal, to see which value fields and reason_values a type accepts - "what does <signal> mean?", "how long does an availability answer stay fresh?"

list_tags

ChatGPT
Purpose: List the candidate tags defined in one organization — user-created (custom) tags and built-in system tags (e.g. Open To Opportunities, Remote Preferred). Call this whenever the user mentions a tag by name — every other tag tool and search_candidates take tag_id, not the name. NOT for: Listing tags on one candidate (they're on the candidate row from search_candidates / get_candidate, and in the response of add_candidate_tags / remove_candidate_tags). Not for the per-hunt scorecard criteria (use get_hunt_criteria). Returns: { data: [{ tag_id, tag_name, is_system }] }. is_system: true tags are applied by Umamy itself and are READ-ONLY: they cannot be renamed, deleted, added to or removed from a candidate. Filters: type — custom (your team's tags), system (Umamy's), or all (default). When to use: - "candidates tagged <X>" → resolve the name here, then search_candidates with filters.tag_id: [<id>] - "what tags do we have?", "which system tags are there?" - Before create_tag (the tag may already exist) and before add_candidate_tags (to get ids)

notify_by_email

ChatGPT
Purpose: Email the user you are working for — the signed-in user in chat, or the agent's creator on a scheduled/Run-now execution. Optionally attach files this agent already saved. Use it to deliver a report, a digest, or a file the user asked to receive by email. NOT for: Emailing a candidate, a colleague, or any other address — you CANNOT choose the recipient and there is no field for one. Candidate outreach is draft_candidate_message (which drafts for human review and never sends). Not for a message the user is already reading in this chat — just reply. Returns: { ok: true, channel: "email", to, attached: [filename] } on success. On a refusal, { ok: false, reason, message } where reason is one of cap_reached, no_email_on_file, no_agent_files, attachment_failed — relay message to the user rather than retrying. When to use: "email me the report", "send me this as an email", "email me the CSV every Friday" (inside a scheduled agent's prompt), "mail me a summary when you're done". Attachments: name the files you already saved with save_artifact in this run; the runtime resolves each name to its content before sending. Call list_agent_files if unsure of a name. Max 3 files, 5 MB total. A chat turn has no saved files — paste the content into body instead. Writing it: body is Markdown — headings, lists and tables render. Lead with the answer; the email is often read on a phone. Do not paste a wall of raw data: put bulk data in an attachment and summarise in the body. Budget: a run may send at most 3 emails. If you hit the cap you get cap_reached; say so and put the rest in your reply.

notify_on_slack

ChatGPT
Purpose: Send a Slack DM to the user you are working for — the signed-in user in chat, or the agent's creator on a scheduled/Run-now execution. Use it for a short "here's what happened" nudge. NOT for: DMing a colleague, posting to a Slack channel, or messaging a candidate — you CANNOT choose the recipient or the channel, and there is no field for either. Long PROSE still belongs in notify_by_email; Slack is for a few lines plus, optionally, a file. Returns: { ok: true, channel: "slack", attached: [filename] } on success. If the DM sent but the file did not, you still get ok: true with attached: [] and an attachment_error — the message DID arrive, so relay the error and point the user at the run page rather than resending. On a refusal, { ok: false, reason, message } where reason is slack_not_linked (the user has not connected Slack — tell them, do not retry), cap_reached, no_profile, or attachment_failed. Relay message. When to use: "ping me on Slack when it's done", "slack me the summary", "slack me the CSV", "DM me if anything looks stuck" (inside a scheduled agent's prompt). Writing it: message is one to three sentences of Slack mrkdwn, rendered after "Hey {name},". Lead with the finding. Pass deep_link (from build_entity_url) when there is somewhere useful to open — it renders as a "View in Umamy" link. Attachments: name the files you already saved with save_artifact in this run; the runtime resolves each name to its content and uploads it into the same DM. Max 3 files, 5 MB total. A chat turn has no saved files. Prefer email when the file is the point and Slack when the nudge is. Budget: a run may send at most 5 Slack DMs. Respect cap_reached and summarise the rest in your reply.

preview_hunt_candidates

ChatGPT
Purpose: Fetch a small sample of candidates that match the hunt's current criteria, to sanity-check the filters before launching. Read-only — these are not yet in the hunt's pipeline. NOT for: Hard counts (use get_hunt_candidate_count). Not for the live candidate pipeline (list_candidates). Returns: { candidates: [...] } — a handful of profiles the search backend would surface today. When to use: "Show me what you'd find with these filters" / "are these the right kind of people?" — typically right before transitioning from requirements to scorecard. Workflow: Requires a backend_job_id like get_hunt_candidate_count — call sync_hunt_draft first if you've never synced.

read_messaging_memory

ChatGPT
Purpose: Read the standing instructions Umamy follows when writing LinkedIn messages — either for one hunt, or the company-wide file. NOT for: The message templates themselves (use get_hunt_configuration) — memory is the set of rules applied ON TOP of a template. Not for messages that were sent (use list_messages). Returns: { content, exists, etag, retired, learned_count, auto_write_enabled, limits }. content is markdown. etag is required to save later — hold on to it. retired lists rules a person deleted, which Umamy will not add back — and neither can you: add_messaging_memory_rule refuses them. If the user wants one back, tell them to type it into the file themselves; the part a person writes is never filtered. auto_write_enabled: false means someone switched off Umamy's automatic learning for this file — the answer to "why has it stopped picking up my edits?". It does not mean memory is off: the file is still read and still applied to every message. Change it with set_messaging_memory_auto_write — a hunt file for anyone, the company file for an org admin. When to use: "What is Umamy remembering for this hunt?", "why does it keep asking about notice period?", "what rules apply to my messages?" Read before any write so you have the etag and do not describe the file from memory. Two layers, and precedence matters: hunt is what is true for this role; organization holds for every hunt at the company. Where they disagree the hunt file wins.

regenerate_hunt_scorecard

ChatGPT
Purpose: Replace the entire scorecard with an AI-generated set grounded in the hunt's hiring brief. NOT for: Tweaking one criterion (generate_scorecard_criterion). Not for committing manual edits (update_hunt_scorecard). Returns: { data: { criteria: [...] } } — the new persisted scorecard. Workflow: The server-side generator already follows get_authoring_guide("scorecard_criterion") rules — no need to fetch the guide before this tool. Fetch it only if you're planning to manually edit the result via update_hunt_scorecard afterwards. When to use: First-time scorecard creation, or when the brief changed materially.

restore_messaging_memory_version

ChatGPT
Purpose: Put a previous version of a memory file back, as a new version. Returns: { content, etag, restored_from }. When to use: The user wants a change undone and has seen what they are going back to. Show them the version's date (and ideally its content) before calling this — restoring blind can drop rules added since. The history is never rewound: this writes the old content forward, so the versions in between remain readable. Rules a person retired after that version stay retired — restoring does not resurrect them. The company-wide file is not restorable here, whoever you act for — a restore replaces the whole file, so it is a write like any other.

search_candidates

ChatGPT
Purpose: The candidate listing tool. Searches an organization's candidate pool with sort, multi-value filters, free-text query, and cursor pagination. Works equally well for "show me hunt X's pipeline" (set filters.job_id) and "everything across my hunts" (omit filters.job_id). Each row carries the slim candidate profile plus its hunt context (job_name, job_status, job_id). NOT for: One candidate's full record (use get_candidate). Internal notes (use get_candidate_notes). LinkedIn message threads — they are not included on rows; call get_candidate_messages(org_id, hunt_id, candidate_id) per candidate when the user needs the conversation. Returns: { ok, candidates: [...], next_cursor? }. Each candidate is the slim profile + job_name / job_status / job_id, no messages array. Pages may be short while next_cursor is still present (selective filters can produce sparse pages). Missing next_cursor means no more pages. When to use (real user phrasings that should trigger this tool): - "show me hunt X's pipeline", "who's connected on hunt X?" (set filters.job_id) - "list all my candidates with tag <X>" - "candidates with tags <X>, <Y>, <Z>" - "show me everyone tagged <X> across all my hunts" - "find all approved candidates across my hunts" - "high-fit-score candidates in my pool" - "search my candidates for engineers in Berlin" - "candidates from inbound applications" - "anyone with kanban status <X>" Filters (all optional, AND across fields, OR within an array): - filters.job_id — restrict to one hunt - filters.min_fit_score — umamy_fit_score >= value - filters.status / filters.kanban_status / filters.connection_status — array of values - filters.candidate_source_type — OUTBOUND | INBOUND | ATS_IMPORT - filters.tag_id — match if any tag overlaps Sort: sort_by ∈ {created_at, last_interaction_at, umamy_fit_score}, sort_direction ∈ {asc, desc}. Default: created_at desc. Free-text: query is matched (case-insensitive, prefix) across name, headline, current_company, location, and tag names. Pagination: Pass cursor from the previous response's next_cursor. Changing sort_by / sort_direction mid-walk invalidates the cursor. Default page size is 10 and the MCP tool caps it at 50 (each row is a full candidate profile, so larger pages risk exceeding the 1MB tool-result limit) — page through with cursor to see more. Freshness: Results may lag recent candidate writes by a few seconds (OpenSearch indexing lag). A candidate added in the same session may not appear in the very next call — if you expect a new row and don't see it, wait briefly and retry once before telling the user it's missing. Cross-org: Always returns candidates for one org. To search across orgs, loop: list_organizations → for each id, list_candidates(id) → combine yourself.

search_hunt_entities

ChatGPT
Purpose: Resolve a free-text query into the structured LinkedIn entities required by hunt filters — companies, schools, locations, job titles, job functions, industries. Returns real entity records with the IDs the criteria fields need; never invent these IDs yourself. NOT for: Reading a hunt's current values (get_hunt_criteria). Not for writing filters — pair this with update_hunt_criteria to apply the chosen results. Returns: { results: [{ id, name, logo? }] } (companies/schools/job-functions/industries) or { results: [{ country, region?, city? }] } (locations) — depending on type. Always present the names to the user and let them confirm before writing. When to use: - Whenever the user mentions a company / university / city / industry to include or exclude. - Building include/exclude company lists from a brand ("competitors of Salesforce" → multiple parallel searches). Workflow: search_hunt_entities → confirm with user via suggest_actions (in the in-app chat) → update_hunt_criteria with the returned IDs.

set_hunt_ats_routing

ChatGPT
Purpose: Write the "ATS Sync" card on the Outreach step — what happens to candidates we approve once an ATS is connected. Whether to push them to the ATS at all, which stage they land in, which existing applicant stages we should ignore (don't message them), and whether we should skip anyone already in the recruiter's pipeline. NOT for: Mapping the hunt to a specific ATS job — that's an inbound-sourcing concern (set_hunt_sourcing(ats_connection_pk, ats_job_id)). Not for outreach messages or automation flags. Returns: { success: true, data: <full config> }. When to use: Only relevant when the org has an active ATS connection AND the hunt is mapped to a job in it (list_ats_connections non-empty, get_hunt_configuration.ats_connection_pk set). Otherwise direct the user to connect/map first.

set_hunt_details

ChatGPT
Purpose: Set the hunt's top-level identity — internal name, candidate-facing job title (role_name), and description. These are the three fields the in-app "Edit Hunt Details" dialog owns. Partial: pass only what you want to change. NOT for: Search filters / the hiring brief criteria (update_hunt_criteria). Not the scorecard (update_hunt_scorecard) or outreach copy (set_hunt_message_sequence). role_name here is the job title shown to candidates in messaging ("We're hiring a {role_name}"), not a search criterion. Returns: { updated: [field, …], sync: { ok, error? } }. name/role_name flow to the engine, so the route auto-syncs the draft after a successful write. Length limits (enforced — over-long values are rejected): name ≤ 60, role_name ≤ 60, description ≤ 260. Keep name concise (the role, e.g. "Backend Engineer — Node.js", not a whole brief sentence). When to use: As the first action when setting up a freshly-created hunt that still has the generic placeholder name / no job title / no description (see <current_hunt_details> in context) — derive a tidy name, a candidate-facing job title, and a short description from the setup brief. Also whenever the user asks to rename the hunt or change the job title shown in outreach.

set_hunt_message_automation

ChatGPT
Purpose: Write the "Message Automation" card on the Outreach step — how aggressively Umamy sends without a human in the loop: connection-request approval mode, auto-send-on-accept, auto-send-everything, and the score threshold for auto-approval. NOT for: The actual message templates (set_hunt_message_sequence). Not for sender identity (set_hunt_sender_identity). Not for ATS routing (set_hunt_ats_routing). Returns: { success: true, data: <full config> }. Field notes: - automation_type — gates how connection requests get approved: - VALIDATE_ALL_CONNECTIONS — every request needs a human OK. - AUTO_APPROVE_ALL_CONNECTIONS — send all of them automatically. - AUTO_APPROVE_UMAMY_ANALYSIS — Umamy decides per-candidate based on the scorecard. - auto_send_first_message_on_accept — once the connection is accepted, fire the intro automatically. - auto_send_all_messages — full hands-off mode for the whole sequence. - auto_approve_threshold — fit-score cutoff (0–100) above which candidates are auto-approved when automation_type = AUTO_APPROVE_UMAMY_ANALYSIS. Higher = stricter. When to use: "Run this on autopilot", "lower the auto-approve bar to 70", "I want to validate every connection myself".

set_hunt_message_sequence

ChatGPT
Purpose: Write the actual messages in the outreach flow — connection request note, intro message, follow-ups, and the approve/reject reply templates. Mirrors the "Message Sequence" cards in the Outreach step. NOT for: The automation behaviour around these messages (use set_hunt_message_automation for automation_type / auto-send flags / auto-approve threshold). Not for sender identity (set_hunt_sender_identity). Not for ATS routing (set_hunt_ats_routing). Returns: { success: true, data: <full config> }. Field notes: - initial_message_template — the intro. Putting [ ] placeholders in it opts into LLM personalisation at send time (the backend routes it to *_with_llm automatically). - reminder_outreach_templates_with_llm — array of follow-ups, in order. Replaces the existing list (pass the full set you want, not a delta). - min_days_before_follow_up — cadence between messages in business days. - connection_note_template — short text on the connection request itself (≤ 200 chars on standard accounts, ≤ 300 on premium). - approve_candidate_template_with_llm / reject_candidate_template_with_llm — reply sent when the recruiter approves / rejects a candidate. Validation: Every template field is checked against the canonical placeholder list — any {{ ... }} not in the allowed set rejects the whole write with a hint. Bracket form [ ... ] is unrestricted (those are LLM-personalised at send time). Workflow: Call get_authoring_guide("outreach_message") (and, when writing the connection note, get_authoring_guide("connection_note")) before drafting — it lists the allowed placeholders and tone rules. Then set_hunt_message_sequence(...). When to use: Anything that's "rewrite the intro", "set the follow-ups", "shorten the connection note", "change the reject message".

set_hunt_persona

ChatGPT
Purpose: Assign (or clear) the outbound LinkedIn persona on a hunt — the LinkedIn account the engine will send connection requests / messages from. Writes hunts.persona_id AND triggers an AWS-side hunt sync so the engine picks up the new sender identity in one call. NOT for: Creating a new persona or connecting a new LinkedIn account — that's a UI-only OAuth flow (surface request_linkedin_connect in the in-app chat to start it). Not for the outreach language / booking_link (use set_hunt_sender_identity). Not for the inbound-only configuration's inbound_persona_id on the configuration object (that's set by set_hunt_sourcing). Returns: { success: true, hunt: { id, persona_id }, sync: { ok, error? } }. sync.ok: false is non-fatal — the supabase write succeeded but the engine doesn't see the change yet (usually because the hunt has no backend_job_id yet, i.e. the user has never synced). Tell the user and retry sync_hunt_draft after the first sync lands. When to use: - "Use [Jean] for this hunt" (after list_personas returned at least one). - "Switch this hunt to send from my hiring manager's account." - "Clear the persona on this hunt — I'm flipping it to inbound-only." (Pass persona_id: null.) Workflow: list_personas (to fetch the org's available personas + their LinkedIn account state) → confirm the user's pick via suggest_actions in the in-app chat → set_hunt_persona → navigate_to_step("outreach") so the user can see the picker reflect the change.

set_hunt_sender_identity

ChatGPT
Purpose: Set the "Sender Identity" card values on the Outreach step — the outreach language (en or fr) and the default booking_link (Calendly / Cal.com / etc.) the assistant should weave into messages. NOT for: Assigning the outbound LinkedIn persona / account — use set_hunt_persona for that (it writes hunts.persona_id and re-syncs the engine in one shot). If no persona exists yet for the org, surface request_linkedin_connect first. Also not for the message templates themselves (use set_hunt_message_sequence). Returns: { success: true, data: <full config> }. When to use: - "We're sending in French" → flip language. - "Use this Calendly link for all hunts on this role" → set booking_link.

set_hunt_sourcing

ChatGPT
Purpose: Flip the hunt's sourcing mode (inbound / outbound / both) and, for inbound, point it at the right source — an ATS connection + job, a LinkedIn job posting, or a LinkedIn Recruiter hiring project. Partial: only the fields you pass are written. NOT for: Outreach content / automation / ATS routing settings (use the set_hunt_message_* / set_hunt_ats_routing tools). Not for connecting a brand-new ATS or LinkedIn account — those are OAuth flows handled in-app; the chat surfaces the connect buttons via the local request_*_connect tools. Returns: { success: true, data: <full config> } — the persisted snapshot after the update. When to use: - "Make this hunt outbound only" / "switch to inbound + outbound". - "Hook the inbound pipeline up to my Greenhouse 'Senior Engineer' job". - "Use my LinkedIn job posting / hiring project for inbound". Workflow: get_authoring_guide("sourcing") (mode-picking + inbound-source rules) → get_hunt_configuration (see current state) → set_hunt_sourcing(...) → navigate_to_step("sourcing") (so the user sees the picker reflect the change) → sync_hunt_draft (so counts/previews pick up the new settings). Pairings to keep in mind: - sourcing_settings.inbound_enabled = true ⇒ also set one of the inbound sources below. - linkedin_job_posting_id and linkedin_hiring_project_id are mutually exclusive — passing one clears the other on the route. - ats_connection_pk + ats_job_id go together; you can list them via list_ats_connections / list_ats_jobs first.

set_messaging_memory_auto_write

ChatGPT
Purpose: Turn Umamy's automatic learning on or off for one hunt's memory file. Off means Umamy stops ADDING rules it infers from the user's edits. This does NOT turn memory off. The file is still read and still applied to every generated message — including every rule already in it. "Stop learning from my edits" and "stop applying what I wrote" are different requests and this is only the first. Say so when you report back, because a user who thinks they switched memory off will be surprised by their next draft. Returns: { layer, auto_write_enabled, changed, version_id }. changed: false means the file was already in that state — that is a success, not a failure; do not retry. When to use: "stop picking up my edits", "stop changing this by yourself", "you can learn from my edits again". Also the fix when read_messaging_memory shows auto_write_enabled: false and the user is asking why their edits are not being learned. You do not need the file's content or its etag — this changes the switch and nothing else, so it cannot drop a rule. The company-wide file needs an org admin. Switching off company-wide learning changes how every hunt at the organization is written, so confirm the user means the company file rather than just this hunt before calling it with layer: "organization". A non-admin is refused — say an admin has to do it in Settings → Memory.

sync_hunt_draft

ChatGPT
Purpose: Push the hunt's current criteria + config to the AWS engine so candidate-count / preview tools work, and so the configuration changes take effect — WITHOUT launching the hunt. Always sends status: "draft"; the route refuses any other status from this tool. NOT for: Launching the hunt live — that stays a deliberate human action in-app (POST /hunts/:id/sync with status: "live" is intentionally not exposed). Not for changing criteria values (update_hunt_criteria) — sync ships whatever's already on the row. Returns: The hunt's updated record, including a freshly-populated backend_job_id if this is the first sync. When to use: - First time after the user has provided enough criteria to query — unlocks get_hunt_candidate_count / preview_hunt_candidates. - After non-trivial criteria or config edits, before re-running counts. Side effects: Idempotent; safe to call repeatedly.

update_candidate_note

ChatGPT
Purpose: Edit the content of an existing team note. Replaces the note's text entirely and stamps updated_at. NOT for: Creating a new note (use create_candidate_note). Not for removing a note (use delete_candidate_note). Returns: { note } — the updated note. When to use: User says "fix that note", "change my note on X to say …", "update the note I just added". Authorization: Only the note's author can edit it — editing someone else's note returns a 403. Get the note_id from list_notes / get_candidate_notes / a previous create_candidate_note result. Tagging a teammate: same as create_candidate_note — @<profile_id> from list_org_members, inside the replacement text. The content you send REPLACES the old text, so a mention you leave out is removed.

update_hunt_criteria

ChatGPT
Purpose: Set values on one or more existing hunt_criteria rows by field_slug. Each update names ONE slug and AT MOST ONE value column appropriate to that field's type. Partial: untouched columns are left alone. NOT for: Adding/removing criterion templates (those are seeded at hunt creation). Not for writing the scorecard (update_hunt_scorecard). Not for the outreach / sourcing config (update_hunt_configuration). Not for entity-search values you haven't resolved — call search_hunt_entities first to get real IDs. Returns: { updated: [field_slug, …], skipped: [{ field_slug, reason }, …], sync: { ok, error? } }. The route auto-syncs to AWS after a successful write, so get_hunt_candidate_count works immediately afterwards — no need to call sync_hunt_draft separately for criteria edits. Validation against criteria_field_templates happens at sync time: a bad value surfaces as sync.ok: false with the engine's error message. Which column to send for which field type (read these off get_hunt_criteria first): - text → field_value (string) - select / multiselect / tags → field_value_string_array (array of option values) - entity-search (companies, schools, …) → field_value_search_criteria_array ([{ id, name, logo? }]) - location → field_value_location ([{ country, region?, city? }]) - structured_object (degree-timing-filter, …) → field_value as a JSON string When to use: Every time the user gives a piece of the hiring brief that maps to a known criterion slug. Workflow: get_authoring_guide("filters") (editorial rules — order, loosen/tighten levers, what not to filter on) → get_hunt_criteria (slugs + types) → search_hunt_entities if needed → update_hunt_criteria (auto-syncs) → get_hunt_candidate_count.

update_hunt_scorecard

ChatGPT
Purpose: Replace the hunt's scorecard with a new set of criteria — each { id, text, is_mandatory, rank }. This is a full replace, so include every criterion you want to keep (read first via get_hunt_scorecard). NOT for: Drafting from scratch — use regenerate_hunt_scorecard (whole thing) or generate_scorecard_criterion (one at a time). Not for evaluating candidates against the scorecard. Returns: { data: { criteria: [...] } } — the persisted set. When to use: After the user confirms a draft, or to apply ranking / pinning edits. Workflow: get_authoring_guide("scorecard_criterion") (signal-based phrasing rules, char limits, examples) → get_hunt_scorecard → modify → update_hunt_scorecard. Aim for ≥ 3 criteria grounded in the hiring brief.

update_messaging_memory_file

ChatGPT
Purpose: Replace a hunt's whole memory file. For reorganising or rewording several rules at once. NOT for: Adding one rule — use add_messaging_memory_rule, which cannot accidentally drop a line. Reach for this only when the user asked for a rewrite or a clear-out. Returns: { content, etag, retired_keys }. retired_keys are rules your save REMOVED: Umamy will not add those back, which is what the user expects when they delete something, so say which ones went. When to use: "tidy up the memory for this hunt", "remove the rules about salary", "rewrite these as three shorter rules". You MUST read the file first and pass its `etag`. If someone else saved in between, the call answers 409 — re-read, re-apply the user's intent to the NEW content, and try again. Never retry with the old content: that discards their change. Preserve what you were not asked to touch. Everything in the file was put there by a person or learned from their edits. Dropping a rule the user did not mention is the failure mode of this tool. The company-wide file needs an org admin, and a whole-file rewrite of it touches every hunt at the company at once — the highest-blast-radius call on this surface. Confirm explicitly before using layer: "organization" here, and prefer add_messaging_memory_rule when the user wants one company-wide rule rather than a rewrite.

update_tag

ChatGPT
Purpose: Rename a custom tag. Every candidate carrying it shows the new name (candidates reference tags by id). NOT for: Changing which candidates carry a tag (use add_candidate_tags / remove_candidate_tags). System tags cannot be renamed (403). Returns: { tag: { tag_id, tag_name, is_system } }. 409 = another tag already has that name. When to use: "rename the <X> tag to <Y>", "fix the typo in the <X> tag".

upload_file_to_hunt

ChatGPT
Purpose: Store a text document against a hunt (or against no hunt at all) so the agent can read it later — a job description, a brief, a scoring rubric, a list of target companies. NOT for: Binary bytes. This takes UTF-8 TEXT only; MCP has no way to send a file. If the user gives you a PDF or Word document that YOU can already read, extract its text yourself and pass it as `content` — do not send them to the web app for something you can do here. Say that the stored copy is your extracted text rather than the original file, so they know the original is not kept and can upload it in the app if they want it archived. Only fall back to asking them to upload in the app when you genuinely cannot read the document. Returns: The stored file — file_id, filename, size_bytes, extraction_status (always ready, since text needs no extraction), created_at. Visibility — worth stating to the user: a file given a hunt_id is visible to everyone in that organization working the role. A file with NO hunt_id is visible only to the person who uploaded it. When to use: The user pastes a job description or brief into the conversation and wants it kept with the hunt, rather than re-pasting it every time. Workflow: list_organizations → list_hunts(org_id) → upload_file_to_hunt(org_id, hunt_id, filename, content) conversation_id: optional, and usually omit it. It links the file to a specific chat thread; an MCP client has no Umamy conversation to link to, so leaving it out is correct there. Cross-org: One org per call. hunt_id must belong to organization_id.

Capabilities

Writes

App Stats

69

Tools

ChatGPT

Platforms

Works with

ChatGPT

Data refreshed daily