Skip to main content
Glama

Theirstack Search

theirstack_search

Use to find companies hiring for a role / tech stack / headcount-band. The return shape includes company.domain directly — feed that to record_search_results as identifier without an Apollo round-trip. For installed-base discovery — companies that already use a technology, rather than hire around it — use find_companies_by_tech_stack instead.

Costs 0.5 Sliq credits per job posting returned; a failed search is free.

In chat, STATE THE COUNT AND THE COST in the same reply as the results — every time, without stopping to ask first. Use the user's number when they gave one, otherwise the default: "pulled 25 postings — 12.5 credits; say if you want more, max 100." Price the postings actually returned: fewer than limit costs less.

On a scheduled scan there is no reply to state it in: pass the number the user chose at setup, carried in the scan spec as theirstack_limit.

Provide at least one of job_title_keywords, job_title_exact, description_keywords, or industry — without some kind of scope filter, searches return too many results. Empty results are normal; broaden filters and call again.

Each returned job's job_description is truncated to 250 chars (with an ellipsis suffix). The full posting body is often 1-3KB; the agent only needs a short excerpt for the signals[i].summary field, and the full body would bloat the LLM context across multiple rounds.

When this runs in an agent, every company with a domain is saved to the workspace Output tab as an agent_search_results company row (deduped by domain, entity_type='company'), carrying its hiring postings in data.signals and its firmographics. Re-running updates rows in place. Pass list_name to name their list; absent, they land in the 'default' list. To filter the companies, record a verdict on each saved row with record_search_results, under the domain it was saved with. A dict with jobs (list), count (int), and created_identifiers (the domains of the companies this call newly added to the list, not ones already on it). Each job carries job_title, job_description (truncated to 250 chars), url, date_posted, and a company dict (name, domain, industry, employee_count, annual_revenue_usd, linkedin_url, country, city). On upstream failure (timeout / 5xx / auth / quota), it returns jobs=[], count=0, and theirstack_available=False so the agent can degrade to other sources.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoJob postings this ONE call returns, 1-100 (values outside are clamped), default 25. On a scan, the spec's `theirstack_limit` is the whole run's budget across every hiring signal — divide it by the number of signals to get this call's number, don't pass it whole to each. There is no pagination on this endpoint, so this is the whole ceiling for a given filter set.
persistNoDefault True — in an agent, the companies are saved as company rows automatically (see above). Pass False only for a read-only lookup the user doesn't want on a list.
agent_idNoOptional — a specific agent to save the companies into. Omit it to use the running agent, which is the usual case. Only a throwaway lookup outside any agent returns postings without saving.
industryNoLinkedIn-style industry names the company must match (e.g. ["Real Estate", "Architecture and Planning"]). Values are validated against TheirStack's canonical catalog (431 industries) — wrong variants like "Architecture & Planning" raise ModelRetry with close-match suggestions.
list_nameNoShort kebab slug naming the Output-tab list bucket (e.g. 'hiring-sdrs'). Absent, companies land in the 'default' list.
company_cityNoCity/state substrings the company HQ must match (e.g. ["Atlanta", "Chicago", "Washington"]). Case-insensitive substring match, OR-combined across the list. Do NOT include the `(?i)` flag — TheirStack rejects it on this field. Pair with `company_country_codes` to keep matches scoped.
lookback_daysNoDays of job-posting history to scan (default 14). A news scan passes the window from its trigger prompt.
job_title_exactNoExact-match titles (TheirStack-indexed; use for short terms like "Snowflake" or "Chief" where regex would over-match).
min_revenue_usdNoMinimum company annual revenue in USD (e.g. 10_000_000 for $10M+).
company_keywordsNoConcepts the company hires around, drawn from TheirStack's keyword catalog (e.g. ["Computer Vision", "Large Language Model (LLM)"]; slugs like "computer-vision" also work). Matches a company when any of its job postings mention the keyword, so it describes what the company does. Use it for verticals the industry taxonomy has no entry for — artificial intelligence, machine learning, computer vision. Values are resolved against the catalog; an unknown one raises ModelRetry with close matches rather than silently matching nothing.
industry_excludesNoIndustry names to exclude. Same canonical-name validation as `industry`.
job_country_codesNoISO alpha-2 — where the job is posted (e.g. ["US"]).
job_title_excludesNoRegex partial-match phrases to filter out of titles.
job_title_keywordsNoRegex partial-match phrases on title (case-insensitive).
max_employee_countNoMaximum company headcount.
min_employee_countNoMinimum company headcount.
description_excludesNoRegex partial-match phrases to filter out of job bodies (e.g. ["intern", "contractor", "1099"]).
description_keywordsNoRegex partial-match phrases on the job body.
company_country_codesNoISO alpha-2 — where the company is HQ'd.
company_description_keywordsNoRegex partial-match phrases on the company's own description — what the company says about itself, as opposed to `description_keywords`, which matches the job posting body. Already case-insensitive, so pass the phrase as-is. Pair it with `company_keywords` and an employee band: on its own each of the two admits staffing agencies and job boards, which both self-describe in the vertical's language and post jobs mentioning every technology.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / persist / description
      Previous value: -"Default True — in an agent, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself."New value: +"Default True — in an agent, the companies are saved as company\nrows automatically (see above). Pass False only for a read-only\nlookup the user doesn't want on a list."
  2. Changed3 schema fields changed
    • addedInput schema / properties / agent_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Optional — a specific agent to save the companies into. Omit\nit to use the running agent, which is the usual case. Only a\nthrowaway lookup outside any agent returns postings without saving."
      +}
    • changedInput schema / properties / persist / description
      Previous value: -"Default True — in a task, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself."New value: +"Default True — in an agent, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself."
    • removedInput schema / properties / task_id
      Removed value: -{
      -  "anyOf": [
      -    {
      -      "type": "integer"
      -    },
      -    {
      -      "type": "null"
      -    }
      -  ],
      -  "default": null,
      -  "description": "Optional — a specific task to save the companies into. Omit\nit to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns postings without saving."
      -}
  3. Changed3 schema fields changed
    • addedInput schema / properties / list_name
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Short kebab slug naming the Output-tab list bucket (e.g.\n'hiring-sdrs'). Absent, companies land in the 'default' list."
      +}
    • addedInput schema / properties / persist
      Added value: +{
      +  "default": true,
      +  "description": "Default True — in a task, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / task_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Optional — a specific task to save the companies into. Omit\nit to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns postings without saving."
      +}
  4. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover readOnlyHint=false and destructiveHint=false; the description goes well beyond that, disclosing credit cost (0.5 per posting, failed searches free), the 250-char truncation of job_description, automatic persistence of company rows to the Output tab with dedupe/update-in-place semantics, and degradation behavior on upstream failure (jobs=[], theirstack_available=False). This is exactly the kind of context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a scannable summary and a separate returns block, and every paragraph carries distinct information. It is nonetheless long, and the elaborate chat-count/cost statement protocol reads more like a policy directive than tool-selection guidance, adding bulk. Structure is good but the size is at the upper bound of what earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 20-parameter tool with no output schema, the description supplies a full returns shape (jobs, count, created_identifiers, per-job fields, the embedded company dict) plus failure semantics, so nothing an agent needs to call or interpret the result is missing. Coverage is complete for the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the prose adds real cross-parameter semantics the schema doesn't: the requirement that at least one scope filter be present, the interaction between `limit` and the scan's `theirstack_limit` budget (divide across signals, no pagination), and the advice to pair `company_description_keywords` with `company_keywords` and an employee band to filter out staffing agencies. These are operational choices the schema alone doesn't convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a specific verb (Search), a concrete resource (recent job postings via TheirStack), and states the downstream purpose (company discovery by hiring signal). It explicitly distinguishes itself from the sibling `find_companies_by_tech_stack` by contrasting hiring-based vs installed-base discovery, so the agent can route without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states when to use it (hiring for a role/tech stack/headcount band), names the alternative and the condition that selects it (`find_companies_by_tech_stack` for installed-base), and gives an explicit negative precondition — at least one of job_title_keywords/job_title_exact/description_keywords/industry is required or results flood. It even advises what to do on empty results (broaden and retry).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources