- Brand
- Umamy
- Category
- HR & Recruiting
- Primary Subcategory
- Applicant Tracking & Recruiting Workflow
Integration details
Description
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.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Applicant Tracking & Recruiting Workflow
- Secondary Subcategories
- None listed
- Brand
- Umamy
- Access
- Account required
- First tracked
- 2026-06-06
- Tool count
- 63
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for Umamy
Get updates when Umamy’s Discoverability Score or category rank changes.
Competing in ChatGPT Interview Intelligence & Candidate Sourcing
View Category63 tools agents can invoke
**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_candidate_tags
**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.
add_messaging_memory_rule
**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`.
apply_role_template
**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 the matching engine 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_hunt_criteria_list
**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.
build_entity_url
**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_candidate_note
**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_hunt
**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
create_tag
**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_agent
**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_candidate_note
**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.
delete_tag
**Purpose:** Create a new DRAFT hunt by copying an existing one's criteria, scorecard, outreach configuration, sourcing settings, language, and (by default) the hunt-setup AI chat history. 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 (`create_hunt`). 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.
duplicate_hunt
**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_by_email
**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.
generate_scorecard_criterion
**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_agent
**Purpose:** Fetch the in-house authoring rules + canonical variable list for one kind of artifact you're about to write (role kickoff, 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. - At the START of a setup conversation (a fresh role) → `kind: "role_kickoff"` — how to research the company + team and which clarifying questions to ask. - 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_authoring_guide
**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 row carries `job_name` / `job_status` plus the underlying `hunt_id`). **Workflow:** list_organizations → list_candidates(org_id, …) → get_candidate(org_id, hunt_id, candidate_id)
get_candidate
**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 (use `query` for a name search) to grab the `hunt_id` tag. **Workflow:** list_organizations → list_candidates(org_id, …) → get_candidate_messages(org_id, hunt_id, candidate_id)
get_candidate_messages
**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:** `{ data, mentioned_profiles }`, oldest first; each note carries `id`, `hunt_id`, `candidate_id`, `content`, `author_id`, `author_name`, `created_at`. **Showing a note to a human:** mentions live inside `content` as a raw `@<profile-uuid>` — substitute `mentioned_profiles[<uuid>].display_name` before quoting a note back, and prefer `author_name` over `author_id`. Never show the uuid. The note's own `id` is likewise for follow-up calls (update / delete) only — **never print it**; a uuid means nothing to a customer. When several notes look alike and the user may need to pick one, number the list (1, 2, 3…) and keep the ids to yourself. **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_candidate_notes
**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 filters.job_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
**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_analytics_summary
**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 `list_candidates` after launch). Not for the count right after a write — `update_hunt_criteria(return_count: true)` returns the very same number inline as `count`, so a write followed by this call is a wasted round-trip. Not for "what would the pool be if I tightened X" — that is `update_hunt_criteria(dry_run: true)`. **Returns:** `{ count: number }` — the estimated outbound pool size (`totalResults` from the same preview the Brief's candidate-count widget shows). Requires the hunt to be synced; if you get a "must sync first" error, call `sync_hunt_draft` and retry. **When to use:** To re-read the current pool without writing anything — at the start of a calibration pass, or after a change made outside this conversation. **Workflow:** `get_hunt_candidate_count` (baseline) → `update_hunt_criteria(dry_run: true, …)` to attribute → `update_hunt_criteria(return_count: true, …)` to apply (the count comes back with it).
get_hunt_candidate_count
**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_configuration
**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 writing values — pair this read with `update_hunt_criteria` (entity values are set by name, no ID lookup). **Returns:** `{ criteria: [{ field_slug, field_name, field_type, is_required, options, 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`. `options` is `[{ value, label }]` for select / multiselect fields and `null` otherwise; a select value is written as `field_value_string_array` holding option `value`s (never labels, never invented values — `seniority` and `excluded-seniority`, for instance, share the nine canonical levels listed there). 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` (entity/location fields are set by name).
get_hunt_criteria
**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 the matching engine /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 → CANDIDATE_HIRED, plus 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_distribution
**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_overview
**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 the matching engine /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_hired` — net hires: +1 when a candidate moves to CANDIDATE_HIRED, −1 when a hire is undone or rejected - `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. **Trend questions:** Pass `compare_to_previous_period: true` together with **absolute ISO** `date_from` + `date_to` (`YYYY-MM-DD`) to also get `previous_total` + `delta` + `delta_pct` for the same-length window immediately before this one — answers "is X improving / declining?" in one call. Relative offsets like `-30d` aren't accepted in compare mode; resolve them to an ISO date yourself first. **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_metrics_timeseries
**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_scorecard
**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` + the matching engine's `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, checks: [{ key, 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. `checks` breaks `complete` into the named sub-requirements behind it (the item is complete iff every check is) — each is a `{ key, complete, params? }` code with NO human-readable text (clients localize the key themselves). `params` is optional, machine-readable, explicitly-keyed detail you can quote straight to the user. Read it to tell the user *exactly* which lever is missing (e.g. `automation_type_set: false`, or `required_criteria_filled` with `params.missing: ["job_title","location"]`) instead of a vague "not done". `finished` is true only when every returned item is complete. **Check keys** (what each `checks[].key` means + its `params` — the payload ships only codes, so use this as your glossary): - `sourcing_mode_selected` — outbound and/or inbound sourcing is enabled. `params`: `inbound_enabled`, `outbound_enabled`. - `inbound_source_mapped` — (inbound hunts only) a source is mapped. `params`: `has_ats_source`, `has_linkedin_job_posting`, `has_linkedin_hiring_project`. - `required_criteria_filled` — every criterion the hunt's role template marked must-have has a value. `params.missing`: array of field slugs still empty (the exact fields to fill) — empty once all are filled, including when the template marked none, which is common and not a problem. - `scorecard_min_criteria` — the scorecard has enough criteria. `params`: `current` (count so far), `required` (minimum). - `linkedin_persona_assigned` — a LinkedIn persona is assigned to the hunt. - `intro_message_written` — the initial outreach message is set. - `outreach_language_set` — the outreach `language` is set. - `automation_type_set` — a connection-approval `automation_type` is chosen (`set_hunt_message_automation`). **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`, inspect its `checks` to see which sub-requirement is failing, then follow its `instructions` (which calls out `get_authoring_guide(kind)` + the right write tool) to fix exactly that → re-call `get_hunt_setup_checklist` after meaningful writes to surface progress.
get_hunt_setup_checklist
**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.
get_organization_metrics
**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 `set_hunt_sourcing`. **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_connections
**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 `set_hunt_sourcing` (`ats_connection_pk` + `ats_job_id`). **Returns:** `{ ats_jobs: [...] }`. **When to use:** Right before `set_hunt_sourcing` (inbound-with-ATS) or `set_hunt_ats_routing` (send-approved-to-ATS).
list_ats_jobs
**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_agents
**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, and the hunt icon (icon_department, icon_custom, icon_color). 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(org_id, filters.job_id=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_hunts
**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) and a `message_id`. Outdated messages are filtered out. A message with status "waiting for client confirmation" (`MESSAGE_WAITING_CONFIRMATION_CLIENT`) is a pending draft — its `message_id` can be passed straight to `send_candidate_message` to send it (no need to re-draft). **When to use:** User asks "how many messages went out this week?", "show me recent replies", "list outbound messages on hunt X", or "send the message I have pending for candidate Y" (find the pending draft here, then send its `message_id`). **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_messages
**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_messaging_memory_versions
**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:** `{ data, mentioned_profiles }`. Notes are ordered most-recent first, each with `hunt_id`, `candidate_id`, `content`, `author_id`, `author_name`, `created_at`. **Showing a note to a human:** `content` stores each mention as a raw `@<profile-uuid>`. Substitute the name from `mentioned_profiles[<uuid>].display_name` before you quote a note back — never show the uuid. Use `author_name` rather than `author_id` for who wrote it. But when you EDIT a note keep the raw `@<profile-uuid>` in the text you send: writing the name back would drop the mention. The note's own `id` is likewise for follow-up calls (update / delete) only — **never print it**; a uuid means nothing to a customer. When several notes look alike and the user may need to pick one, number the list (1, 2, 3…) and keep the ids to yourself. **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_notes
**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_org_members
**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_organizations
**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: [...] }` — each persona carries its `persona_id`, the LinkedIn account display `name` (e.g. "Connor Blair"), and live connection status (`connected` / `needs_reconnect`). 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_personas
**Purpose:** List the candidate tags defined in one organization — user-created (custom) tags and built-in system tags (e.g. `ATS Duplicate - Skipped`, `ATS - Graded`, `Open To Opportunities`). **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)
list_tags
**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_files_for_hunt
**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.
read_messaging_memory
**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_file_content
**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.
regenerate_hunt_scorecard
**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".
update_tag
**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.
restore_messaging_memory_version
**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_messaging_memory_file
**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_candidates
**Purpose:** Resolve a free-text query into the structured entities required by hunt filters — companies, schools, locations, job titles, job functions, job subfunctions, industries, languages. Returns real entity records; never invent these 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? }] }` for every `type`. LOCATION resolves through a geocoder; INDUSTRY / JOB_FUNCTION / JOB_SUBFUNCTION / LANGUAGE resolve against the CANONICAL filter-value catalogs — for these, the returned `name` is the exact value the candidate search filters on, so write it verbatim into `field_value_search_criteria_array` via `update_hunt_criteria` (a hand-typed variant matches nobody). 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 / job function / spoken language 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 names.
search_hunt_entities
Umamy ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Umamy's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Umamy alternatives on ChatGPT?
As of 2026-11-03, Umamy competes with BrightHire, Clera, DiSCPROFiL, FaceFirst Interview Builder, Hunr, Interview Feedback Extractor, JobMojito, Juicebox and 8 more in ChatGPT Interview Intelligence & Candidate Sourcing, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.


