MCP App Store
by KeenEthics
Productivity
RealtyAds icon

RealtyAds

by RealtyAds, LLC

Overview

The RealtyAds MCP connection allows you fully manage all your RealtyAds properties. All functionality available in the RealtyAds portal is now available directly in ChatGPT. You can create and edit ads, save ads for later use, view performance and pull reports, and ask product and support questions.

Tools

add_companies_to_targeting

ChatGPT
Bulk version of add_company_to_targeting: add many companies to one RealtyAds account's targeted-firms list in a single call. Each entry needs a company_name; linkedin_company_url and/or linkedin_company_id are strongly recommended for an immediate match. Use this tool when the user wants to add several companies to a RealtyAds account's targeted-firms list in one go (e.g. "target these 10 companies", or running through a list pasted from a spreadsheet). Per-entry behavior is identical to add_company_to_targeting. Required: - account_id: the RealtyAds property account that should target the companies. Must be an account the caller has access to. - companies: a JSON array of objects, each containing: - company_name (required): used verbatim as firm_name (max 150 chars). - linkedin_company_url (optional, recommended): "https://www.linkedin.com/company/jll/" or just "/company/{slug}". - linkedin_company_id (optional, recommended): numeric LinkedIn company id. Up to 100 entries per call. Behavior per entry (same as add_company_to_targeting): - When a LinkedIn id is supplied or resolved from the URL, writes a fully-matched row immediately and clears any matching exclusion / partner-pursuit / partner-integration suppression flags. - When only a name is supplied, writes a lightweight row and lets the matching cron resolve the LinkedIn record on its next pass. - Idempotent on (account_id, linkedin_company_id): duplicates return action_taken="already_targeted" without creating extra rows. Errors are per-entry, not per-batch. A bad row (e.g. empty company_name) returns action_taken="failed" with an "error" string for that entry; the rest of the batch still runs. The top-level response carries counts (added / already_targeted / failed) so the caller can see at a glance what happened. Each entry's response carries the input position (index) plus the same fields the singular tool returns (action_taken, targeted_firm_id, linkedin_company_id, matched_via, canonical_company_name, queued_for_matching). This is a WRITE action. Every successful add is logged in the portal activity feed under the calling user.

add_company_to_targeting

ChatGPT
Add one company to a RealtyAds account's targeted-firms list so the ad system starts targeting decision-makers at that company. company_name is required; linkedin_company_url and/or linkedin_company_id are strongly recommended for an immediate match. Use this tool when the user wants to start targeting a specific company through their RealtyAds account. The company is added to the same targeted-firms list the portal's "Company Targeting" UI writes to. Required: - account_id: the RealtyAds property account that should target this company. Must be an account the caller has access to. - company_name: the company name to add. Used verbatim as the firm_name on the row. Strongly recommended (provide whatever you have — both is fine): - linkedin_company_url: a full LinkedIn URL like "https://www.linkedin.com/company/jll/" or a "/company/{slug}" tail. The slug can be a vanity name ("jll") or a numeric id. - linkedin_company_id: numeric LinkedIn company id, when you already know it from search_companies, lookup_company_information, get_companies_reached, etc. When a LinkedIn id is supplied (or resolved from the URL via our local cache), the row is written fully matched so ad delivery picks it up immediately, the company is removed from any active exclusion list, and partner-pursuit / partner-integration "do not target" flags for the same company are cleared. When only a name is supplied, the row is queued for the matching cron — typically resolved within an hour, but the company isn't actively targeted until then. Returns: - action_taken: "added", "already_targeted", or (rare) an error message - targeted_firm_id: the id of the new row in realty_client_targeted_firms (or the existing active row, when already_targeted) - account_id, company_name, canonical_company_name - linkedin_company_id: the id used on the row, when resolved - matched_via: "linkedin_company_id" | "linkedin_company_url_numeric" | "linkedin_company_url" | "name_only" - queued_for_matching: true when we wrote a name-only row that the cron still needs to match Idempotent on the (account_id, linkedin_company_id) pair: re-adding a company that's already actively targeted returns action_taken="already_targeted" and does not create a duplicate row. Re-adding by name alone (no LinkedIn id) does not de-duplicate — every name-only call creates a fresh row, matching the portal's "manual entry" precedent. This is a WRITE action. It is logged in the portal activity feed under the calling user.

add_contact_to_targeting

ChatGPT
Add one contact to a RealtyAds account's targeted-users list so the ad system starts targeting that person. Clears an existing per-contact exclusion when present. Provide at least one of: contact_id, linkedin_profile_url, work_email, personal_email, cell_phone, or the trio first_name + last_name + company_name. Use this tool when the user wants to start targeting a specific individual through their RealtyAds account. The contact is added to the same targeted-users list the portal's "Decision-Maker Targeting" UI writes to, and ads from this account will start including them. Required: - account_id: the RealtyAds property account that should target this contact. Must be an account the caller has access to. - exactly one of the identifier groups below. Identifier groups (provide whichever you have — more specific is better): - contact_id: internal RealtyAds contact id (e.g. from lookup_contact_information, get_company_decision_makers, get_website_visitors) - linkedin_profile_url: full LinkedIn URL like "https://www.linkedin.com/in/jane-smith/" or the "linkedin.com/in/jane-smith" tail. We synchronously look the profile up so the contact is fully populated before being added. - work_email: e.g. "jane.smith@cbre.com" - personal_email: e.g. "jane@gmail.com" - cell_phone: digits-only is fine; we normalize for matching. - first_name + last_name + company_name: all three required if you go this route. Optional context (job_title, city, state) improves the stub record we create when no existing match is found. If the contact is already on file we attach the targeting row to that record. If they're not on file, we create a new contact record from the fields you provided and attach to that — background enrichment will then fill in additional details over the next few minutes. If the contact was previously on the account's exclusion list, that exclusion is cleared. Returns: - action_taken: "added" or "already_targeted" - targeting_user_id: id of the new row on the targeting list (or the existing row, when already_targeted) - audience_data_contact_id: internal contact id used on the targeting row - account_id, first_name, last_name, job_title, company_name, linkedin_profile_url - resolved_via: which input identifier matched — "contact_id", "linkedin_profile_url", "work_email", "personal_email", "cell_phone", "name_and_company", or "created_new_contact" - created_stub_contact: true when no existing contact matched and we created a new record - message: short human-readable summary Idempotent on the (account_id, audience_data_contact_id) pair: re-adding a contact that's already actively targeted returns action_taken="already_targeted" and does not create a duplicate row. This is a WRITE action. It is logged in the portal activity feed under the calling user.

add_contacts_to_targeting

ChatGPT
Bulk version of add_contact_to_targeting: add many contacts to one RealtyAds account's targeted-users list in a single call. Per-entry behavior matches the singular tool — supply at least one identifier (contact_id, linkedin_profile_url, work_email, personal_email, cell_phone, or name+company). Use this tool when the user wants to add multiple contacts to a RealtyAds account's targeted-users list in one go — for example after pasting a CSV of contacts they want targeted, or applying a list of decision-makers exported from another system. Per-entry behavior is identical to add_contact_to_targeting. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - contacts: a JSON array of objects (up to 100 entries). Each entry needs at least one of: - contact_id (internal RealtyAds contact id) - linkedin_profile_url (full URL or "linkedin.com/in/..." tail) - work_email - personal_email - cell_phone - first_name + last_name + company_name (all three required when going this route) Optional context fields per entry: job_title, city, state — used to populate the contact stub when no existing match is found. Per-entry behavior (same as add_contact_to_targeting): - Resolves the contact via the priority order above (contact_id → linkedin_profile_url → work_email → personal_email → cell_phone → name+company). - When no existing contact matches, creates a stub audience_data.contacts row from the supplied fields and attaches the targeting row to it. - Idempotent on (account_id, audience_data_contact_id): re-adding an already-targeted contact returns action_taken="already_targeted" without creating duplicates. - Clears the contact from the account's exclusion list when present. - Logs "Updated Decision-Maker Targeting" / "Added a decision-maker to targeting" with action_source="mcp". Errors are per-entry, not per-batch. A bad row (no identifier supplied, etc.) returns action_taken="failed" with an "error" string. The top-level response carries counts (added / already_targeted / failed) so the caller can see at a glance what happened. This is a WRITE action. Every successful add is logged under the calling user and shows up in the portal activity feed.

add_custom_job_title_to_targeting

ChatGPT
Add one custom job title to the targeted-job-title list for a RealtyAds account so decision-makers with that title at the account's target companies become eligible to see ads. Looks up the LinkedIn title id from a job-title name when one is not supplied. Use this tool when the user wants RealtyAds AI to specifically target a job title across the account's target companies — "add VP of Real Estate to our job-title targeting", "we want to reach Heads of Workplace", "target Chief People Officers". What it does (mirrors the portal): - Looks up the LinkedIn-canonical title id from the supplied name (cached typeahead; falls back to live LinkedIn API when cache is cold). - Skips silently if the title is already on the account's targeting list (idempotent). - INSERTs into r1.realty_client_targeted_job_titles. - Logs to r1.user_change_log under "Updated Job Title Targeting" with action_source="mcp". The portal itself does not log this action — MCP writes are logged for AI auditability. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - title_name: the job title to add (e.g. "VP of Real Estate"). Should be a common LinkedIn-style title — uncommon phrasings may not resolve to a LinkedIn id. Optional: - title_id: the LinkedIn title id (numeric tail of urn:li:title:<n>, as a string). Use when you already know it (e.g. from a prior add response's title_id). When omitted, the tool will look it up by name. Returns: - action_taken: "added", "already_targeted", or "no_match" (if the typeahead couldn't resolve a title id) - row_id, account_id, title_id, title_name - matched_via: "explicit_title_id" / "linkedin_cache" / "linkedin_api" - candidates: up to 5 alternative typeahead matches (returned only when there's ambiguity, so the caller can confirm the right one was picked) - message: short human-readable summary Pair with get_ad_targeting (which lists current job titles in filters.job_titles, each with a row_id) and remove_custom_job_title_from_targeting (which takes the row_id). This is a WRITE action.

add_geography_to_targeting

ChatGPT
Add one geography (city / metro / region) to the targeted-geographies list for a RealtyAds account so people and companies in that geography become eligible to see ads. Looks up the LinkedIn geo id from a name when one is not supplied. Use this tool when the user wants to expand targeting to a new geography — "also target tenants in Jersey City", "add Greenwich, Connecticut to our targeting", "we want to reach brokers in Atlanta". What it does (mirrors the portal): - Looks up the LinkedIn-canonical geo id from the supplied name (cached typeahead; falls back to live LinkedIn API when cache is cold). - Skips silently if the geography is already on the account's targeting list (idempotent on (account, geo_id)). - INSERTs into r1.realty_client_targeted_geos with target_cre=1, target_companies=1 by default (matching the portal default — geo applies to both broker reach and company reach). - Logs to r1.user_change_log under "Updated Geography Targeting" with action_source="mcp". The portal itself does not log this action — MCP writes are logged for AI auditability. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - geo_name: the geography to add. Be as specific as possible — "Atlanta, Georgia, United States" resolves more reliably than just "Atlanta". Optional: - geo_id: the LinkedIn geo id (numeric tail of urn:li:geo:<n>, as a string). Use when you already know it. - target_cre: (default true) include this geography when targeting commercial real-estate brokers. - target_companies: (default true) include this geography when targeting companies / decision-makers. Both can be set to false, but at least one should be true or the row has no effect. Returns: - action_taken: "added", "already_targeted", or "no_match" (if the typeahead couldn't resolve a geo id) - row_id, account_id, geo_id, geo_name, target_cre, target_companies - matched_via: "explicit_geo_id" / "linkedin_cache" / "linkedin_api" - candidates: up to 5 alternative typeahead matches (returned only when there's ambiguity) - message: short human-readable summary Pair with get_ad_targeting (which lists current geos in geography.submarkets, each with a row_id) and remove_geography_from_targeting (which takes the row_id). This is a WRITE action.

add_media_to_library

ChatGPT
Add a file to the account's media library from a public http(s) URL. Additive — it never deletes or overwrites existing media. Returns the new asset's id and CDN URL. Fetches the file at source_url (a public http(s) URL) and stores it in the account's media library. Optionally attach a short description and tags to help the user find it later. This is additive — it never deletes or overwrites existing media. Use list_media_library to see what is already there. Returns the new media_id, cdn_url, and media_type. This is a WRITE action.

check_targeting_status

ChatGPT
Definitive check whether a specific contact or company is on an account's active target list, exclusion list, or neither. Returns a boolean membership result — no list scanning needed. Always prefer this over searching get_ad_targeting results when you need to verify a single entity's targeting status (get_ad_targeting lists are capped, so absence from that sample does not prove an entity is untargeted). Provide account_id plus at least one of contact_id (checks the individual target/exclusion lists) or linkedin_company_id (checks the company target/exclusion lists). You may pass both. The response includes on_target_list / on_exclusion_list booleans and, for companies, previously_targeted (soft-removed) plus target/exclusion detail blocks when present.

create_new_saved_ad

ChatGPT
Build a new, non-live saved ad with custom copy and optional media-library images. Use media ids returned by list_media_library or add_media_to_library. When media_ids is omitted, the campaign's current images are inherited. The name is only the saved-ad label, not the ad copy. The result is not live; call launch_saved_ad separately after explicit user confirmation.

duplicate_saved_ad

ChatGPT
Duplicate an existing saved ad into a new, non-live saved ad on the same campaign. Copies the source saved ad exactly, including copy, media, and landing page. Use update_saved_ad afterward only when the duplicate should differ.

exclude_company_from_targeting

ChatGPT
Add one company to a RealtyAds account's exclusion list so ads on this account will never target the company, even when other targeting signals would otherwise match. Stronger than remove_company_from_targeting — works even when the company isn't on the targeting list yet. Use this tool when the user wants to permanently keep a company off ads on a RealtyAds account — "exclude Microsoft from targeting", "we never want our ads to reach Apple employees", or to block a known-irrelevant tenant or competitor. Difference vs remove_company_from_targeting: - remove takes a company OFF the active targeting list. The matching cron could later re-add them based on other signals. - exclude is sticky — the company is registered as "do not target" so the matching cron, partner-pursuit signals, and other paths can't bring them back. Use exclude when the user really doesn't want this company ever targeted on this account. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - linkedin_company_id: numeric LinkedIn company id (use search_companies or lookup_company_information to resolve a name first). - company_name (optional but recommended): used as the firm_name on the exclusion row when supplied; otherwise we use the canonical name from RealtyAds' LinkedIn cache. Side effects (matches portal exactly): - INSERTs a row on the exclusion list. - Deactivates any matching active row on the targeted-firms list (so the company drops off active targeting immediately). - Scrubs the company from r1.linkedin_company_targeting. - Sets partner-pursuit and partner-integration "block" flags on for the (account, company) pair — unconditional, no guard. Returns: - action_taken: "excluded" or "already_excluded" - excluded_firm_id, account_id, linkedin_company_id, company_name - message: short human-readable summary Idempotent: re-excluding a company that's already on the exclusion list returns action_taken="already_excluded" with no writes. This is a WRITE action. Logged in r1.user_change_log under the calling user with action_source="mcp".

get_account_optimization

ChatGPT
An account's setup and optimization assessment: a one-stop summary, per topic, of whether the account is fully optimized or has a specific opportunity to get more value out of RealtyAds. Reports, per topic, whether the account is fully optimized or has a specific opportunity — targeting setup (competition set / individual / company), the Insights Tag, integrations (CRM / deal-flow via Partner Hub), report engagement, content freshness, and the Chrome/Edge extension. Also returns last_content_change: the account-level datetime of the most recent ad content edit (Updated Ad Text / Images / Video / Loaded Saved Ad) from the same user_change_log the portal dashboard uses. Present even when the Content tip is "Fully Optimized" and does not name the date in prose; null when no edit has been logged. Use this for "what can I do to optimize my account?", "is my account set up right?", "when was content last changed?", or before nudging on infrastructure so you only recommend what's genuinely missing (never tell a client to connect a CRM they already have). Requires account_id.

get_account_performance

ChatGPT
Monthly reporting card for one account, with totals and insights across each active RealtyAds service. Use when the user asks 'how did account X do in <month>?', 'what were last month's ad results?', 'who visited the website in May?', 'is the broker reach hitting target?', or any other broad performance question. Requires account_id. year_month defaults to the most recently completed calendar month (previous month); pass YYYY-MM (e.g. 2025-08) to drill into a specific historical month. Response sections (each omitted when there's no data for the period): - digital_ads_stats: totals (hyper_targeted_views, unique_companies_reached, frequency, clicks), broker_reach (target % vs actual %, YTD avg, months beating target), tenant_reach (target / actual companies, multi-month engagers), per-ad-network breakdown. - website_stats: page_views, unique_visitors, new_visitors, returning_visitors, total_companies, new_companies, high_engagement_companies, desktop_visitors, mobile_visitors (all sourced from the same monthly cache the portal renders), plus identified_contacts and decision_makers_identified (deduped by contact_id) and a companies-that-also-saw-ads cross-signal. services_reported advertises the section names emitted in this response (e.g. ["digital_ads_stats", "website_stats"]) so the LLM can look up the matching block directly. Always returns the period metadata (is_current_month, is_partial), the account's services_active_now, and a headline prose summary the LLM can quote directly. Sections about services that weren't active or had no data are omitted entirely — absence means 'no activity', not 'zero'. Response also carries two curated, per-topic prose blocks: - insights_summary (keys: overview, current_month, broker_reach, tenant_reach, website_services, digital_ads_upsell, website_services_upsell) — mirrors the portal AI Assistant's response to 'How is this account performing?'. - optimization_insights (keys: intro, functionality, website_analytics, report_engagement, content, integrations, chrome_edge_extension; special: not_active / no_digital_ads) — mirrors the portal AI Assistant's response to 'How can I further optimize this account?'. Quote these back to the user verbatim. Do NOT paraphrase or re-derive these sentences from the raw structured fields — the prose is deliberately curated to lock in tone, accuracy, and the specific RealtyAds value-prop framing (broker reach vs industry standard, projection-vs-actual sentiment, multi-month engagement, content rotation cadence, etc.). The prose paragraphs contain markdown links ([label](url)) to RealtyAds reporting pages, pursuit-profile drill-downs, browser-extension stores, and Account Manager mailto: addresses — the same set of links the portal AI Assistant renders. Pass them through verbatim. Each paragraph also has a parallel <key>_references array (e.g. tenant_reach_references, functionality_references) listing every link as {label, url, type} for clients that render plain text or want to format links differently. Link type is one of: report (portal page), pursuit_profile (company drill-down on the tenant reach report), download (XLSX/file), mailto (AM email), extension (browser extension store). Note on vocabulary: hyper_targeted_views is the RealtyAds-branded name for ad impressions delivered through the account's targeting filters. Use it instead of generic 'impressions' when describing the data to a user.

get_activity_report

ChatGPT
Generate the same Activity Report (PDF and/or XLSX) the portal produces. Chronological platform activity (AI optimizations, client actions, account-manager events) for one or more accounts in a given month. Use this tool when the user wants the monthly Activity Report — "what happened on the account last month", "send the activity report PDF", "give me the activity feed as Excel". What it does: - Pre-fetches /ajax/account_activity_feed/?method=read (POST) for the month with context=main. - For PDF: posts the standard stats payload to /reporting/downloads/generate_pdf/. The PDF template re-fetches activities server-side using account_id_list + year_month. - For XLSX: hits /ajax/budget_optimizations/?method=read_xlsx with activity_feed_only=true (shared endpoint with AI Report, distinguished by the flag). Inputs: - account_ids (optional): subset to include. Defaults to all the caller's accounts. - year_month (optional): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Lite-account gating: the portal blocks Activity Report when ALL selected accounts are Lite. This tool surfaces the portal's rejection as a tool error. Activity data is only available from 2024-08 onward (per portal logic).

get_ad_campaigns

ChatGPT
List the account's ad campaigns with each campaign's property, active state, current ad copy, saved ads (saved_ad_id + name), and rotation-schedule state. For each campaign returns its internal campaign_id (plumbing — never print it to the user), the property it advertises, whether it is active, the current ad copy, its saved ads (each with a saved_ad_id and name), and its rotation-schedule state. This is the entry point for ad management — call it first to get the campaign_id and saved_ad_id values used by the saved-ad authoring, launch, and scheduling tools. Requires account_id.

get_ad_creative_performance

ChatGPT
Compare an account's ad creatives by hyper-targeted views and site visits for a date range. Use this tool for creative-level ad performance: which creative performed best, how individual ads compare, or whether image or video creatives performed better in a date range. Requires account_id, start_date, and end_date (YYYY-MM-DD). Creatives are ordered by hyper-targeted views. Returns one row per creative plus by_creative_type totals and per-creative averages. hyper_targeted_views is the RealtyAds name for ad impressions. site_visits matches the Ad Report's client-facing label for ad clicks. Use the metric the user asked about when naming a winner; do not claim one creative or media type performed better overall when the returned metrics disagree. Use get_ads instead when the user wants ad copy, media, landing pages, or previews. Use get_account_performance for broad account-level reporting.

get_ad_targeting

ChatGPT
Read every targeting setting currently applied to a RealtyAds account — who is being targeted (industries, job titles, company sizes), where (submarkets, geo-fencing), the account's target audiences, and the account's target / excluded company and individual lists. Use this tool when the user asks any question about who the account is currently targeting — "who are we targeting?", "are we targeting tech companies?", "what's on the target list?", "is power prospecting on?", "how big is the exclusion list?", "are we hitting submarket X?", or as a sanity-check before suggesting changes via the targeting write tools. Returns a single bundle that mirrors the portal's Targeting Settings page, restructured into plain-English fields. Empty sections are omitted entirely (absence means "not configured" / "not applicable" — never "zero"). Top-level fields always present: - account_id, account_name, account_category (Office / Multi-Family / Industrial / Retail / Medical / Life Science / Land), optimization_goal ("reach" or "clicks") - headline: one-sentence summary the LLM can quote verbatim ("Currently targeting commercial real estate brokers and tenants in the market across 7 industries; 142 companies and 5 individuals on the targeted lists; 2 companies excluded.") - target_audiences: the proprietary RealtyAds audiences the account is reaching (Commercial Real Estate Brokers, Tenants In The Market, Asset Managers & Property Owners, Residential Real Estate Brokers, and the bookkeeping entries for the account's own uploaded lists). Each carries group_id, name, and included: true/false. Use the group_id with update_target_audience_groups_targeting to flip a group on or off. - filters: industries (each {industry_id, name}), detailed_categories (vertical-specific sub-segments, when applicable; each {industry_id, name}), job_titles (each {row_id, title_id, name} — use row_id with remove_custom_job_title_from_targeting), company_size_buckets. industry_id values are reusable with update_industry_targeting. - geography: submarkets (each {row_id, geo_id, name} — use row_id with remove_geography_from_targeting), geo_fencing (bool), competition_buildings (address list), colleges (multi-family only). - growth_signals: power_prospecting, coworking_targeting, funding_targeting, site_selectors_targeting — each a bool. Use update_growth_signal_targeting to flip any of these. - vertical_specific (only when applicable): live_work_play_targeting, ideal_tenant_targeting (multi-family), life_science_investor_targeting, life_science_funding_stages, life_science_clinical_stages. Use update_vertical_targeting to flip the bool toggles (geo_fencing, live_work_play_targeting, ideal_tenant_targeting, life_science_investor_targeting). - target_companies: { total, shown_in_response, has_more, next_offset?, companies[] }. Each company carries targeted_firm_id (use with set_company_targeting_priority / remove_company_from_targeting), company_name, linkedin_company_id (when LinkedIn has been matched), date_added, and priority (always present: "low" / "normal" / "high" / "boosted"; "normal" is the default). Sorted newest-first; capped at 200 per response with has_more=true when the full list is larger — page the rest via the list arg (see Pagination below). Match-quality internals are not exposed; do not speculate about how well a row is matched. - target_individuals: { total, shown_in_response, has_more, next_offset?, individuals[] }. Each individual carries targeting_user_id (use with set_contact_targeting_priority / remove_contact_from_targeting), first_name, last_name, job_title, company, location, priority (always present: "low" / "normal" / "high"; "normal" is the default), date_added. Email and phone are intentionally NOT included — pulling those is out of scope for this read tool. - excluded_companies: { total, shown_in_response, has_more, next_offset?, companies[] }. Each carries company_name, linkedin_company_id, date_added. Use unexclude_company_from_targeting (with linkedin_company_id) to remove a company from this list. - alwa…

get_ads

ChatGPT
List the ad creatives for an account (currently-running and saved), including ad text, media, landing page URL, and a portal preview URL. Use this tool when the user asks about their ads — what they currently look like, what saved versions exist, what ad copy or images are being used, what the landing page is, or to get a preview URL for a specific creative. Returns one entry per ad creative tied to the account. Each entry includes: - id: stable saved-template id (use as a reference in follow-up questions) - name: display name for the creative - status: "live" (currently running), "saved" (a saved option), or "archived" - creative_type: "image" or "video" - ad_text: the ad copy shown to viewers - call_to_action: button text (e.g. "Learn More", "Schedule a Tour") - media: ordered list of image URLs for image ads, or [video_url] for video ads - landing_page_url: where clicks send the viewer - preview_url: public RealtyAds page that renders the ad inside a phone-bezel mockup with buttons to switch between Facebook, Instagram, LinkedIn, Google, and X previews. Safe to share with the end user — no login required. - date_created: when that saved version was first stored (not the account-level last content edit) - last_updated: when that creative's content was last saved, including Edit Ad Media / campaign-block saves when the saved-template row itself has no usable timestamp The live ad is always returned first, followed by saved options newest-first. Archived ads are excluded unless include_archived is true. Defaults to all live + non-archived saved creatives for the account. Pass account_id (required) and optional include_archived (bool) and limit (int, default 50, max 200). Do not use this tool for creative-level performance metrics — use get_ad_creative_performance. Use get_account_performance for broad account-level reporting. Do not use it to list companies who saw the ads — use get_companies_reached.

get_ai_report

ChatGPT
Generate the same AI Report (PDF and/or XLSX) the portal produces for one account and one month. Includes per-platform spend, views, clicks, AI optimization counts, and the exposure chart. Use this tool when the user wants the monthly AI Report — "send the AI report for [account]", "what did RealtyAds AI do last month", "I want the AI optimizations PDF". What it does: - Pre-fetches three AJAX endpoints the portal JS gathers: 1. /ajax/budget_optimizations/?method=read — per-platform stats 2. /ajax/account_activity_feed/?method=read — AI activity (counted by platform) 3. /ajax/budget_optimizations/?method=react_chart_exposure — exposure chart series - For PDF: posts the assembled payload to /reporting/downloads/generate_pdf/. - For XLSX: hits /ajax/budget_optimizations/?method=read_xlsx with the same scope. Inputs: - account_id (required). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Lite-account gating: portal blocks AI Report for Lite accounts (server-side in generate_pdf). When the account is Lite, the bridge call fails with the portal's "this report is unavailable on the current subscription" message — surfaced as a tool error.

get_broker_reach_report

ChatGPT
Generate the same Broker Reach Report (PDF and/or XLSX) the portal produces for one account and one month. Includes monthly + YTD reach stats and the broker roster with targeting tactics. Use this tool when the user wants the monthly Broker Reach Report — "send the broker reach for [account]", "I want the broker roster as Excel", "give me the broker reach PDF". What it does: - Pre-fetches /ajax/broker_reach_report/?method=performance_stats for the account + month. - For PDF: posts to /reporting/downloads/generate_pdf/ with the full reach-stats payload. (The portal's per-page handleShare path is broken — it omits broker_stats and the template exits. This tool uses the working Reporting Center payload shape.) - For XLSX: posts read_xlsx to /ajax/broker_reach_report/ with the same stats. Inputs: - account_id (required). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Returns a text summary containing the direct download URL(s) (always show them to the user) + one or two resource_link items, 24-hour expiry. Gating: blocked when broker reach is disabled for the account (no broker or audience targeting configured). The tool surfaces a clear error rather than producing an empty report.

get_companies_reached

ChatGPT
List companies reached by an account's ads for a month range, including why each company was targeted. Use this tool when the user asks which companies saw or engaged with their ads, who was reached in a month or range of months, or why specific companies were included in their targeting. Requires account_id. start_year_month and end_year_month are optional YYYY-MM values; when omitted, both default to the most recently completed calendar month. These inputs select whole calendar months, not an exact rolling-day window. response_mode controls the shape: - "monthly" (default) returns one independent canonical Tenant Reach result per calendar month. Filters, sorting, and limit are applied separately to each month. - "combined" groups a multi-month range by company before sorting and limiting. It returns unique_companies_reached, repeat_engager_count, the account-level period total, and each company's months_reached plus server-computed aggregate Hyper-Targeted Views. A one-month combined request is normalized to the standard monthly response. Monthly mode may span up to 12 calendar months. Combined mode may span up to 3 consecutive calendar months. Never add monthly company counts or capped monthly views yourself. Use combined mode when the user wants one unique company list or aggregate view across multiple calendar months. In monthly mode, the default sort_by="relevance" preserves the Tenant Reach Report and PDF's default highest-relevance order (targeting method, explicit priority, and decision-maker availability). In combined mode, "relevance" ranks sustained reach first (months_reached, then aggregate views). Use sort_by="views_desc" only when the user asks for "most views", "most engaged", or "top engagers by views"; use "views_asc" for least views. tenant_filter mirrors the report's Advanced Search segments: - "all" (default) — every successfully engaged tenant - "ai_prospecting" — only tenants identified through AI Prospecting - "client_targeting" — only tenants engaged from client targeting Apply tenant_filter="ai_prospecting" whenever the user explicitly asks for AI-prospected engagers. Do not infer that a high-engagement or client-targeted company is an AI prospect. Monthly output mirrors the Tenant Reach Report company list and caps per-company hyper_targeted_views at "500+". Combined output never exposes an uncapped per-company aggregate: totals through 500 are exact, totals from 501–999 are "500+", and totals of 1,000 or more are "1,000+". The account-level period total remains exact, matching the quarterly report overview aggregate. Company rows also include the LinkedIn company page, estimated company size from LinkedIn profile counts, 6/12/24 month employee growth rates when cached, industry, website URL, compact reasons_targeted keys, targeting_reason_definitions, and targeting_priority. Each company includes reasons_targeted (array of keys). targeting_reason_definitions maps those keys to reporting titles and descriptions for this response — use it for labels; do not invent reason names. A company can have multiple reasons. These keys explain why a company was targeted; tenant_filter uses the same primary classification as the report's Advanced Search. Common reason keys (not exhaustive; prefer definitions in the response when present): - ai_prospecting — AI Prospecting: companies surfaced by RealtyAds AI from decision-maker / opportunity signals - list_upload — Broker Prospecting: companies from a client-uploaded target list - detailed — Detailed Industry Prospecting: companies prioritized for selected industries - activity_report — Activity Report Prospecting - partner_hub — Partner Hub / CRM-linked prospecting - competition — Competition Prospecting - funding — Funding Prospecting - proprietary — Proprietary Audience Prospecting Do not use this tool for website visitors; use get_website_visitors for website-specific traffic. Do not use it for broad account performance summaries; use get_account_performance.

get_company_decision_makers

ChatGPT
Return the top decision-makers at a company, ranked by the same pursuit-profile algorithm the portal uses. Requires a LinkedIn company id or LinkedIn company URL. Default 3 contacts, max 15. Use this tool when the user wants the most relevant senior contacts at a specific company — "who should we reach out to at JLL?", "give me the top decision makers at Acme", or any prelude to a follow-up like add_contact_to_targeting (coming soon). Required (pass exactly one): - linkedin_company_id: numeric (the id you already have from search_companies, lookup_company_information, get_companies_reached, etc.) - linkedin_company_url: a full LinkedIn URL like "https://www.linkedin.com/company/jll/" (or just the "/company/{slug}" tail; numeric slugs work too) Resolution order: linkedin_company_id wins, then linkedin_company_url. Optional: - max_decision_makers: default 3, max 15. Caller can ask for a deeper list when the user wants more candidates. Anything above 15 is clamped. Returns: - linkedin_company_id, company_name, linkedin_profile_url, resolved_via - decision_makers_shown, max_decision_makers (the cap that was applied) - decision_makers: array of contacts, each with contact_id, first_name, last_name, job_title, linkedin_profile_url, profile_image_url, work_email, phone, follower_count. Empty contact-info fields are omitted from each entry. Ranking is the same pursuit-profile algorithm the portal renders: targeted-user boost, country / state proximity, account-specific exclusions, executive-assistant / inflated-follower penalties, and ChatGPT-DM augmentation. Because the algorithm is account-scoped, the tool automatically picks one of the caller's accounts as the ranking context — preferring an account that's actively targeting the company, then one that has reached the company via ads, then any active account, then any account. If none of the caller's accounts has a signal for the company (relevant when the caller is an internal RealtyAds user with no account-specific context), the decision_makers array is empty and a warnings entry explains why. Do not use this tool to find a company by name — that's search_companies. Do not use it for the full company profile bundle — that's lookup_company_information (which also embeds up to 10 decision-makers if you only need a quick top-of-list view alongside other company info).

get_company_engagement

ChatGPT
How a single company is engaging an account's properties: a month-by-month trend of that company's hyper-targeted ad engagement (impressions), with first-engagement date, averages, peak month, and largest swings. Use this for "how is this company engaging my properties?". Does NOT require a connected CRM or activity-report upload — prefer this over get_deal_attribution for plain engagement questions. Returns engaged:false (not an error) when the company has no recorded ad engagement on the account yet. Requires account_id and the company's linkedin_company_id (the same id returned by get_companies_reached, search_companies, get_top_prospects, etc.). The response includes total_months, total/average monthly engagements, peak_engagement, largest_increase / largest_decrease, and a monthly_timeline array. For a specific period total, pass both start_year_month and end_year_month in YYYY-MM format. The response then includes requested_period.total_hyper_targeted_views, computed from the returned monthly data. The current incomplete month is excluded.

get_contact_email_and_phone

ChatGPT
Get a contact's work email and phone numbers by contact_id (preferred), LinkedIn URL, email, or contact_id_partner_network (credit-gated; consumes 1 lookup credit per successful enrichment). Use this tool when the user explicitly asks for someone's email address, phone number, or contact details. This is a credit-gated operation that consumes one credit per successful lookup from the user's included monthly allowance. Pass whichever identifier you have (exactly one), preferring contact_id when you have one: - contact_id: use when you already have one from a prior tool call (e.g. get_company_decision_makers, get_website_visitors, get_companies_reached). Preferred — fastest and unambiguous. A contact_id_partner_network is a different id; never pass one in this field. - linkedin_profile_url: a full LinkedIn URL like "https://www.linkedin.com/in/jane-smith/" or the "linkedin.com/in/jane-smith" tail. The contact is resolved automatically before enrichment. - email: a work or personal email address. Looked up against existing contacts. - contact_id_partner_network: the id returned by search_contacts. The contact is resolved automatically before enrichment. Returns the person's work email, mobile phone, and work phone. The data is enriched synchronously via multiple contact-data providers, with smart merge logic (provider agreement beats domain match beats fallback). Enriched values apply to this response only and are not written back to the shared contact record. Each returned field carries a confidence label in the confidence map (high / medium / low) — relay low confidence to the user as "possibly outdated" rather than presenting the value as certain. Credit reporting: credit_charged states whether THIS lookup consumed a credit — lookups that find no contact information are free. credits_used_this_month and credits_remaining_this_month are the user's month-to-date running totals, NOT the cost of this call; a single lookup never costs more than 1 credit. If credits are exhausted, the tool returns an error explaining the limit.

get_contact_engagement

ChatGPT
How a single person is engaging an account's properties — their employer's company-level ad-engagement trend (a proxy) plus person-level website-visit, deal-pipeline, and targeting signals. IMPORTANT: LinkedIn ad impressions are tracked at the company level, not per individual — so the ad-engagement trend returned (employer_ad_engagement) is this person's CURRENT EMPLOYER's month-by-month engagement (the best available proxy), not the individual's own impression count. Present it that way to the user. On top of that company proxy, this adds genuinely person-level signals: whether THIS person specifically has been identified visiting the account's website (count + last visit), whether they appear in the account's connected-CRM deal pipeline, and their targeting status + priority. Use this for "how is this person engaging my properties?", "how active is this broker with us?", or "what's this contact's engagement?". Returns employer_ad_engagement.engaged=false (not an error) when the employer has no recorded engagement, and flags when the person has no linked employer company. Requires account_id and contact_id (the audience_data id from lookup_contact_information / current_target — NOT a contact_id_partner_network). If individual_visitor_data_restricted is set, person-level website visit verification was suppressed for this account. Cannot verify that a specific individual visited the website due to local privacy laws; company visitor lists and decision-makers at those companies remain available.

get_deal_attribution

ChatGPT
Returns RealtyAds' influence and engagement timeline across all active tenant deals on a RealtyAds account, grouped by tenant company. Includes monthly engagement, current deal stage, decision-makers reached, and (when known) employee growth signals. Requires a connected CRM or an activity-report upload within the last 6 months. Use this tool when the user wants the deal-attribution picture for a RealtyAds account — "what deals are we influencing?", "which pursuits has Kalshi turned into?", "which tenants are we reaching and what stage are they in?", or any question that pairs deals with RealtyAds engagement. Required: - account_id: the RealtyAds property account to report on. Must be an account the caller has access to. Optional: - linkedin_company_id: drill down to one tenant company. - deal_stages: comma-separated list of stages to include, e.g. "Tour" or "Proposal,LOI". Default excludes Dead and Lease Executed; recommend leaving as default unless the user is hunting for a specific stage. - limit: max companies returned. Default 25, max 100. The response is sorted with the most-engaged companies first. - include_monthly_timeline: default true. Set false to skip the per-month engagement bars when only the headline metrics are needed (smaller response). Eligibility: this report needs CRM data to compute deal stages. If the account has neither a connected CRM nor a recent activity-report upload, the response carries: - eligible: false - partner_hub_url and support_email - a message explaining how to enable the report Report scope: fixed 24-month lookback ending in the current calendar month. There is no timeframe input — RealtyAds influence accumulates over a long pursuit cycle, so the report always covers the full window. Response shape: - account_id, report_period (start_month / end_month / lookback_months) - eligibility_source: "crm_integration" or "activity_report_upload" - deal_stage_filter: human-readable summary of what was filtered - total_companies, shown_in_response, has_more - companies[] — each entry contains: - linkedin_company_id, company_name, logo_url, industry, headquarters, locations_count - employee_count, employee_growth_rate_6_month / _12_month / _24_month - current_deal_stage ("Tour", "Proposal", etc.) - deal_data_source[]: the partners that contributed deal data for this tenant — e.g. ["vts"], ["hubspot"], ["salesforce"], ["activity report upload"], or a combination like ["vts", "activity report upload"] when both sources have data. Alphabetically sorted. - crm_deal_ids[] — when at least one source is a CRM (anything other than "activity report upload"), the underlying CRM deal ids associated with this tenant (one company can have more than one) - identified_by_realtyads_ai (bool) - targeted_for_engagement (month label), first_engagement (month label) - months_with_engagement, total_hyper_targeted_ad_views, total_website_page_visits - website_services_active (bool) - decision_makers[]: name, job_title, linkedin_profile_url, profile_image_url - deal_stage_history[]: ordered stages with started_at / last_seen dates - monthly_timeline[]: per-month bars with hyper_targeted_ad_views, website_visits, prospect_boosting_active, deal_stage (when known) The "Identified by RealtyAds' AI" flag is account-level (one stable signal per company), not per-month — same as the portal's pursuit-profile chart legend. Do not use this tool to list every company reached by ads (that's get_companies_reached) or to inspect targeting settings (get_ad_targeting). Use this when the user wants to understand the deal pipeline + RealtyAds' contribution.

get_insights_tag_status

ChatGPT
Read the current portal Insights Tag status for one account. Returns the same finalized Insights Tag status shown in the client portal. Use when the user asks whether the Insights Tag is active for a property. Statuses: active (tracking visitors), no_traffic (tag active on a RealtyAds landing page but no visitors yet), waiting (not activated yet), issue (recently stopped reporting), or stale (stopped reporting a long time ago). last_activity_date is the latest relevant pixel or visitor activity date when available.

get_landing_page

ChatGPT
Read the current account-level landing page URL that ads click through to. Use when the user asks what the current landing page is or where ads click through. This is the account-level destination (custom_url or the RealtyAds lander), not a per-creative override. Do not use get_insights_tag_status or get_ads for that question. Returns landing_page_url, uses_custom_url, source, realtyads_lander_url, and stored_custom_url. Present landing_page_url as a markdown link.

get_leads

ChatGPT
List active leads for an account — individual contacts plus the reasons they qualify as a lead (website visit, CRM, form fill, ad view) and their current lifecycle stage. Use this tool when the user asks about their leads, prospects, or pipeline — who's been engaging with the property, who's in the CRM, who filled out an inquiry, what stage each deal is in, or what the overall lead funnel looks like. Returns one entry per identified person across every lead-generating surface: - identified website visitors (contact_visited_website) - contacts whose employer's company visited the site (company_visited_website) - contacts in the client's CRM (Yardi, VTS, etc.) (in_crm) - contacts who filled out a lead / inquiry form (lead_form_completion) - contacts whose employer was reached by the account's ads (ad_view, inferred at the company level) Each lead carries: - contact_id, first_name, last_name, job_title, company_name, linkedin_company_id - linkedin_profile_url, profile_image_url, company_logo_url (when available) - work_email, phone, city, state - lead_qualifiers: deduplicated array of the keys above; a single person can carry several - lifecycle_stage: one of "lead" (default), "inquiry" (form filled but no CRM stage), "in_market", "tour", "proposal", "legal", "lease_out" (lease document sent for signature), "lease_executed" (signed), "idle" (dormant CRM prospect), or "dead". Derived from the CRM's prospect_status / prospect_raw_status when present, falling back to "inquiry" for form-only leads and "lead" otherwise. - last_activity: most recent timestamp across all sources (visit / form / CRM update) Leads are deduplicated by audience_data contact id when we have it, otherwise by normalized email. The same person across multiple sources becomes ONE row with multiple qualifiers, not multiple rows. Qualifier groups can overlap. A lead_qualifier_definitions lookup is included at the top level so the LLM can show the user a human-readable description for each key without bloating every row. Defaults to year-to-date (January 1 of the current year through today). Pass start_date and/or end_date (YYYY-MM-DD) for any window. Filters: lifecycle_stages (array — return only leads matching one of these stages), qualifiers (array — return only leads that have at least one of these qualifiers, e.g. ["in_crm","lead_form_completion"]), min_certainty (1–5 floor for website-visit identification confidence), limit (default 50, max 200). The default sort_by="lifecycle_stage" surfaces the most advanced lifecycle stages first, then orders by last_activity within each stage. sort_by="last_activity_desc" puts the newest activity first, then lifecycle stage. Sorting is applied before limit. Sorting does not change the date window; use start_date/end_date for a specific timeframe. The date window bounds website visits, company visits, form submissions, and ad-view signals. Active CRM prospects are included regardless of the window, because CRM presence is not a dated acquisition event. Do not use this tool for company-level reach metrics — that's get_companies_reached. Do not use it for raw website visit traffic — that's get_website_visitors. If individual_visitor_data_restricted is set, named visitor-identified leads (contact_visited_website) were suppressed for this account. Cannot verify that a specific individual visited the website due to local privacy laws; company visitor lists and decision-makers at those companies remain available.

get_overview_report

ChatGPT
Generate the same portfolio-level Overview Report (PDF and/or XLSX) the portal produces. Covers all the caller's accounts (or a subset they specify) for one month — reach %, views, consistency, subscription. Use this tool when the user wants a portfolio-level monthly recap across multiple properties — "give me the overview for all my properties for April", "send the portfolio report as Excel", "what's the multi-property snapshot for last month". What it does: - Pre-fetches /ajax/get_account_budgets.php for the month (the only data the portal JS gathers before posting to generate_pdf for Overview). - For PDF: posts the standard portal payload to /reporting/downloads/generate_pdf/. The PDF template re-fetches all per-account stats server-side using realty_report_stats + the budget map. - For XLSX: hits /ajax/overview_reporting_stats.php?method=read_xlsx. Inputs: - account_ids (optional): subset of accounts to include. When omitted, includes every account the caller has access to. Lite accounts are automatically excluded from both PDF + XLSX (matches portal behavior). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Returns a text summary containing the direct download URL(s) (always show them to the user) plus one or two resource_link items. Links expire 24 hours after generation. Lite-account gating: Lite accounts in the caller's portfolio are filtered out before generation. If ALL the caller's accounts are Lite, the report is empty and the tool returns an error.

get_pursuit_case_study

ChatGPT
Generate the same Pursuit Case Study PDF the portal produces from the Pursuit Profile modal for one company. Includes the engagement timeline, targeting graph, ad preview, and impressions for that company against the account. Used heavily for sales enablement. Use this tool when the user wants a per-company case study — "make a case study for Microsoft", "I need the pursuit PDF for Apple on this account", "send the case study for company X". What it does: - Pre-fetches four AJAX endpoints the Pursuit Profile modal calls: 1. /ajax/tenant_reach_report/?method=pursuit_profile — company stats, employee profiles 2. /ajax/tenant_reach_report/?method=company_insights — headcount + growth metrics 3. /ajax/website_analytics/?method=get_interest_profile — engagement timeline graph 4. /ajax/partner_hub/?method=is_crm_pursuit — chooses template (CRM vs standard) - Picks the right PDF template: case_study_pdf.php (standard) or case_study_crm_pdf.php (CRM-integrated pursuit) - Posts the assembled bundle through the bridge to /reporting/downloads/generate_pdf/. Inputs: - account_id (required): the RealtyAds property account context for the case study. - linkedin_company_id (required): the LinkedIn company id of the target. Use search_companies or lookup_company_information to resolve from a company name first. - year_month (optional): context month for the timeline graph; defaults to last complete month. Returns a text summary containing the direct download URL (always show it to the user) + a resource_link to the case-study PDF (24-hour expiry). No XLSX for this report — case studies are inherently visual / prose-heavy.

get_service_snapshot

ChatGPT
Generate the same Service Snapshot PDF the portal produces for one RealtyAds account and one month. The Service Snapshot is the headline broker + tenant reach summary with notable companies. Returns a downloadable PDF link the user can open or save. Use this tool when the user asks for the Service Snapshot — the most-clicked monthly client export. "Give me the Service Snapshot for [property] for [month]", "send the snapshot PDF for last month", "I need the one-pager". What it does: - Pre-fetches the two property_snapshot AJAX endpoints (read + notable_companies) the portal JS calls. - Calls the same /reporting/downloads/generate_pdf/ endpoint the portal does, with the same payload. - The portal renders the PDF via its existing pdfmyurl pipeline and streams it back. - The PDF is saved on the MCP server; the direct HTTPS download URL is included in the text response (share it with the user), and a resource_link content item is also attached for clients that render attachments. Inputs: - account_id (required): the RealtyAds property account. - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (the only supported format — Service Snapshot has no XLSX in the portal). Output: text summary containing the direct download URL (always show this URL to the user — some MCP clients don't render resource_link attachments), plus a resource_link content item pointing at the PDF. The link expires 24 hours after generation. Lite-account gating: the portal blocks Service Snapshot for Lite-tier accounts. If the caller's account is Lite, the generation request will fail at the portal side with a "this report is unavailable on the current subscription" message — that's preserved verbatim and surfaced as a tool error. Doesn't return data fields — for the structured snapshot metrics, use get_account_performance instead.

get_tenant_reach_report

ChatGPT
Generate the same Tenant Reach Report (PDF and/or XLSX) the portal produces for one account and one month. Shows targeted tenants, impressions, engagement stats, and per-company highlights. Use this tool when the user wants the monthly Tenant Reach Report — "send the tenant reach for [account] for [month]", "I need the tenant report as Excel". What it does: - Pre-fetches /ajax/tenant_reach_report/?method=performance_subscription for the supplied account + month. - For PDF: posts the standard payload (subscription stats + identifiers) to /reporting/downloads/generate_pdf/. The PDF template re-fetches the per-tenant list server-side. - For XLSX: posts the same tenants_targeted_xlsx payload to /ajax/tenant_reach_report/. Inputs: - account_id (required). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Returns a text summary containing the direct download URL(s) (always show them to the user) + one or two resource_link items, 24-hour expiry. No Lite-account gating on this report — every account with tenant targeting can produce one.

get_top_case_studies

ChatGPT
Automatically pick an account's highest-relevance Tenant Reach companies and generate a Pursuit Case Study PDF for each — no company id needed. Great for sales enablement when the user just wants "some case studies." Use this tool when the user wants case studies for an account but does NOT name a specific company — "make me some case studies", "generate case studies for this account", "what are my best case studies". What it does: - Auto-selects the account's highest-relevance companies using the Tenant Reach Report/PDF default ranking (get_companies), so the picks match the portal's default order. - The default ranking considers targeting method, explicit priority, and decision-maker availability. It is not a raw Hyper-Targeted Views ranking. - Generates one Pursuit Case Study PDF per company via the same pipeline as get_pursuit_case_study (per-company prefetch, CRM-vs-standard template, bridged PDF generation). Inputs: - account_id (required): the RealtyAds property account context. - limit (optional): how many case studies to generate (default 3, max 5). - year_month (optional): context month for the engagement timelines and company selection window (YYYY-MM). Defaults to last complete calendar month. - linkedin_company_ids (optional): explicit array of LinkedIn company ids to use instead of auto-selecting. Duplicates are removed, then the list is capped at 5. When provided, limit is ignored. When the user asks for case studies from a filtered Tenant Reach segment (such as AI Prospecting) or asks for the companies with the most views, call get_companies_reached first with the requested tenant_filter/sort_by, then pass at most 5 selected company ids here via linkedin_company_ids. Returns a text summary containing the direct download URL of every generated PDF (always show these URLs to the user) + one resource_link per case-study PDF (24-hour expiry). When the user wants a case study for ONE named company, use get_pursuit_case_study instead.

get_top_prospects

ChatGPT
The account's most engaged non-CRE tenant prospects, ranked by depth of sustained ad engagement, with targeting status, priority, growth signals, website-visit flag, and a Prospect Boost recommendation. Each prospect includes its targeting status, targeting priority, employee growth signals, and whether the company has also visited the property website. Also surfaces a prospect_boost_recommendation (the best candidate not already being boosted) and untargeted_engager_count (multi-month engagers not yet on the target list). Use this for sustained multi-month prioritization: "who should I prioritize?", "show me my top prospects", "who should I Prospect Boost?", "which multi-month engagers am I not targeting?", or "suggest firms to add to my targeting". It analyzes all completed months available for the account, requires at least two engaged months, and excludes the current partial month. Do NOT use this to reproduce a selected month's Tenant Reach Report, rank companies by views for a report period, or select generic Tenant Reach case studies. Use get_companies_reached for those requests. Requires account_id. Optional limit (default 10, max 25).

get_visitor_identification_report

ChatGPT
Generate the same Visitor Identification Report (PDF and/or XLSX) the portal produces. Identified website visitors (companies + individuals) for one account, one month. Requires Website Services with Visitor Intelligence. Use this tool when the user wants the monthly Visitor Identification Report — "who visited our site last month", "send the visitor ID PDF", "give me the identified visitors list". What it does: - Pre-fetches /ajax/website_analytics/?method=get_visitors_overview for the date range (eligibility check; also confirms visitors > 0). - For PDF: posts minimal payload to /reporting/downloads/generate_pdf/; the template re-fetches the visitor rows server-side. - For XLSX: hits /reporting/downloads/visitor_identification_xlsx.php. Inputs: - account_id (required). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Gating: - Requires Website Services subscription. - Requires at least one identified visitor in the period (zero-visitor months produce no report).

get_website_analytics_report

ChatGPT
Generate the same Website Analytics Report (PDF and/or XLSX) the portal produces for one account and one month. Includes traffic overview, top locations, page-level data, mobile breakdown, and ad-placement chart. Requires the account to be on Website Services. Use this tool when the user wants the monthly Website Analytics Report — "send the website analytics for [account]", "what was our traffic last month", "give me the analytics PDF". What it does: - Pre-fetches /ajax/website_analytics/?method=get_visitors_overview for the date range (converted from year_month: "2026-04" → "2026-04-01_2026-04-30"). - For PDF: posts the full analytics payload (location_data, page_data, etc.) to /reporting/downloads/generate_pdf/. - For XLSX: hits /reporting/downloads/website_analytics_xlsx.php for the same range. Inputs: - account_id (required). - year_month (optional, YYYY-MM): defaults to last complete month. - format: "pdf" (default), "xlsx", or "both". Gating: - Requires Website Services subscription. If the account doesn't have Website Services, returns "Website Services not enabled". - Returns "no website traffic for this period" when the account had zero page views in the month.

get_website_visitors

ChatGPT
List the individual contacts and companies identified visiting the account's marketing website in a given date window. Use this tool when the user asks who's visited their website, which companies are showing up, who the named visitors are, which decision makers are engaging, or what the website intelligence is telling them. Returns two parallel lists for a single account: - companies: each visitor company with linkedin_company_id, company_name, company_domain, logo_url (LinkedIn company logo when we have it), linkedin_profile_url, interest_level ("high" | "medium" | "low" — same buckets the portal shows: high = top prospects, medium = interested, low = researching), also_saw_ads (true when the company was also reached by paid ads in the same window), total_visits, total_page_views, first_visit, last_visit, city, state, and a pursuit_profile_url that opens the portal's pursuit-profile modal for that company. - contacts: each named individual with contact_id, first_name, last_name, job_title, company_name, linkedin_company_id, linkedin_profile_url, profile_image_url (LinkedIn profile photo when we have it), work_email, phone, certainty_level (string like "4/5" — confidence this person was actually the visitor on a 1–5 scale; 5/5 is highest), last_visit, city, state. Empty PII / image fields are omitted from the response. Contacts are deduplicated by contact_id; if the same person was matched to multiple companies, the highest-confidence / most-recent row wins. Defaults to the last 30 days. Pass start_date and/or end_date (YYYY-MM-DD) for any window. Other filters: include_companies / include_contacts (booleans, both default true) let the caller drop either list to save tokens; company_id scopes contacts to a single LinkedIn company; decision_makers_only narrows contacts to senior decision-makers at the company (RealtyAds' internal decision-maker rank >= 70, not surfaced in the response); min_certainty filters contacts by minimum confidence in the match (1–5); also_saw_ads_only keeps only visitor companies that were also reached by ads in the window; targeted_companies_only keeps only visitor companies on the account's active explicit target-company list; limit (default 50, max 200) caps each list independently. The two company-overlap filters run before the underlying 500-company cap. total_companies_identified and total_contacts_identified report the count returned by the underlying data source for the window — both are capped at 500 by the source layer for performance. If you see 500 for either, narrow the window or apply more filters to see further; otherwise the count is the true total. Do not use this tool for companies who saw the ads (no website visit required) — that's get_companies_reached. Do not use it for ad performance metrics — that's get_account_performance. If individual_visitor_data_restricted is set, named visitor contacts were suppressed for this account. Cannot verify that a specific individual visited the website due to local privacy laws; company visitor lists and decision-makers at those companies remain available.

get_yearly_account_performance

ChatGPT
Yearly reporting card for one account, with totals and insights across each active RealtyAds service. Use when the user asks 'how did account X do this year?', 'what are this year's ad results?', 'how are we doing YTD?', or any other broad yearly performance question. For a single month, use get_account_performance instead. Requires account_id. year defaults to the current calendar year (year-to-date through today); pass a prior year (e.g. 2025) to report Jan 1–Dec 31. Response sections (each omitted when there's no data for the period): - digital_ads_stats: totals (hyper_targeted_views, unique_companies_reached, frequency, clicks), broker_reach (target % vs actual %, months reported), tenant_reach active-month averages (company counts per month) plus vs-target delta, per-ad-network breakdown. - website_stats: page_views, unique_visitors, new_visitors, returning_visitors, total_companies, new_companies, high_engagement_companies, desktop_visitors, mobile_visitors from the yearly visitor-stats cache. Identified contacts, decision-makers, and ad/site cross-signal are omitted because those live lists are capped. services_reported advertises the section names emitted in this response (e.g. ["digital_ads_stats", "website_stats"]) so the LLM can look up the matching block directly. Always returns the period metadata (year, is_partial, and is_future when applicable), the account's services_active_now, and a headline prose summary the LLM can quote directly. Sections about services that weren't active or had no data are omitted entirely — absence means 'no activity', not 'zero'. Response also carries one curated, per-topic prose block: - optimization_insights (keys: intro, functionality, website_analytics, report_engagement, content, integrations, chrome_edge_extension; special: not_active / no_digital_ads) — mirrors the portal AI Assistant's response to 'How can I further optimize this account?'. It describes current account setup, not historical year setup. insights_summary is not included — that prose is monthly-only. Quote optimization_insights back to the user verbatim. Do NOT paraphrase or re-derive these sentences from the raw structured fields — the prose is deliberately curated to lock in tone, accuracy, and the specific RealtyAds value-prop framing. The prose paragraphs contain markdown links ([label](url)) to RealtyAds reporting pages, browser-extension stores, and Account Manager mailto: addresses — the same set of links the portal AI Assistant renders. Pass them through verbatim. Each paragraph also has a parallel <key>_references array listing every link as {label, url, type} for clients that render plain text or want to format links differently. Broker yearly values are monthly averages across completed months in the requested year. Tenant yearly values are monthly averages across active months, defined as months with a projection or actual reach row; active months with zero actual reach remain included, while pre-service and missing months are excluded. These values are not year totals and not always YTD. Historical years use the full calendar year; the current year uses completed months only. If the current year has no completed month yet (January), those blocks use the in-flight current_month and months_reported is 1. Tenant averages are company counts, not percentages. Describe broker reach concisely using actual and target percentages only. Do not separately state the calculated delta or months beating target. frequency is total hyper-targeted views in the requested window divided by companies deduped across that same window (same formula as get_account_performance, longer window). It is not a monthly-average frequency. Note on vocabulary: hyper_targeted_views is the RealtyAds-branded name for ad impressions delivered through the account's targeting filters. Use it instead of generic 'impressions' when describing the data to a user.

launch_saved_ad

ChatGPT
Make an existing saved ad the campaign's live running ad. This visibly changes the client's running ad and queues rebuilt platform creatives. Confirm with the user first, naming the account and property. Pass confirm=true only after that explicit confirmation. Get ids from get_ad_campaigns.

list_accounts

ChatGPT
List the property accounts the caller can access, with full identity, property, subscription, contract, and account-manager context for each. Use when the user asks 'what accounts do I have?', 'which building is account X?', 'who manages this account?', 'when does the contract renew?', or any other broad question about their RealtyAds account set. Returns one rich record per account: name, company, brokerage, ownership, the building (property name + full address), the active services with human-readable package names (e.g. 'Market Entry', 'Visitor Intelligence Agent'), the current contract term with months_remaining, monthly_cost, and auto_renew, and the assigned Account Manager (name, email, phone). The per-account status field is the account's lifecycle stage, not just a binary on/off. Possible values: active (fully running), pending_activation (signed but services haven't launched yet), deactivated (services paused), pending_deactivation (scheduled to end soon), terminated (ended early), cancelled (did not renew), archived (closed), prospect (pre-sale), not_subscribed (account exists in the system but has no services subscribed). Look at each service's active flag in active_services for which specific services are running. A summary block aggregates totals and breakdowns by status, category, and ad package — useful for quick portfolio overviews. The summary is omitted when filtered to a single account. Defaults to active accounts only; pass include_archived: true to include archived ones. Use account_id to narrow to a single account, or limit (default 50, max 200) to truncate large portfolios. The full caller-visible count is always reported in you_have_access_to so the LLM knows whether truncation happened.

list_media_library

ChatGPT
List the account's media library assets (uploaded images, videos, and files) with their CDN URLs, descriptions, and tags. Use for "what's in my media library?", "show me my uploaded photos/videos", or to find an existing asset before referencing it. Requires account_id. Optional media_type filters the set: "media" = images + videos (default), "images", "videos", "files" (non-image/video), or "all". Returns up to 100 assets, each with media_id, file_name, cdn_url, thumbnail_url, media_type, description, tags, and date_created.

lookup_company_information

ChatGPT
Look up detailed information on one company by LinkedIn id, LinkedIn URL, or domain; refreshes employee count and 6/12/24-month headcount growth rates from the LinkedIn data API if our cached data is older than 30 days. Use this tool when you need detailed information on one specific company — beyond what search_companies returns — or when you want to make sure the headcount / growth numbers are fresh. Provide exactly one of: - linkedin_company_id: numeric (the id you already have from search_companies, get_companies_reached, etc.) - linkedin_company_url: a full LinkedIn URL like "https://www.linkedin.com/company/jll/" (or just the "/company/{slug}" tail) - company_domain: the company's website domain ("jll.com", "https://www.jll.com/") Resolution order: linkedin_company_id wins, then linkedin_company_url, then company_domain. Returns one company record: - linkedin_company_id, company_name, linkedin_profile_url, logo_url - website_url, website_domain, primary_email_domain, industry - employee_count, estimated_csuite_employees - hq_city, hq_state, hq_country_code - employee_growth_rates { 6_month, 12_month, 24_month } when populated - cre_tags (["brokerage"], ["owner"], or both) when our CRE classifiers have flagged the company - total_office_locations: integer total count we have for this company (could be in the thousands for global brokerages) - office_locations: array of up to max_locations (default 25, max 200) locations, with HQ first. Each carries formatted_address, street_address, suite, city, state, postal_code, country_code, location_type ("headquarters" or "office"), latitude / longitude when available. - top_decision_makers: array of up to max_decision_makers senior contacts (default 3, max 10) at this company, ranked using the exact pursuit-profile algorithm from the portal — audience_tools::get_decision_makers. The algorithm is account-scoped (targeted-user boost +200, country / state proximity, account-specific exclusions, etc.), so behind the scenes the tool automatically selects one of the caller's accounts as the ranking context — preferring an account that's actively targeting the company, then one that has reached the company via ads, then any active account, then any account. Each entry carries contact_id, first_name, last_name, job_title, linkedin_profile_url, profile_image_url, work_email, phone, follower_count. Empty PII fields are omitted. If none of the caller's accounts has a signal for the company (relevant when the caller is an internal RealtyAds user with no account-specific context), the block is omitted entirely and a warning explains why. - data_freshness: { last_identity_refresh, last_employee_metrics_refresh, last_growth_refresh } as YYYY-MM-DD dates - resolved_via: which input we used to find the row ("linkedin_company_id", "linkedin_company_url", "company_domain", "company_domain_linkedin_api") Pass force_refresh: true to bypass the staleness check and re-fetch from LinkedIn regardless of when we last looked. Useful when the user explicitly asks for the latest figures. Pass max_locations to widen or narrow the office_locations array (1–200), and max_decision_makers to widen the top_decision_makers list (1–10). Returns warnings (and a null company) when the input doesn't resolve to a row in our cache. For long-tail companies that aren't enriched yet, search_companies' linkedin_api fallback can surface a linkedin_company_id you can then feed back into this tool. Do not use this tool to find a company by name — that's search_companies. Do not use it for engagement signals (visits, ad views) — those are per-account and live in get_companies_reached / get_website_visitors.

lookup_contact_information

ChatGPT
Get the full profile for ONE specific commercial-real-estate professional from the partner-network contact directory (LinkedIn + ZoomInfo + Apollo) — title, company, LinkedIn profile, and location. NOT limited to RealtyAds customers — works for any person in the directory. Identify them by contact_id, contact_id_partner_network from search_contacts, or LinkedIn profile URL. Cached results within 30 days are served without re-querying the source. Use get_contact_email_and_phone to retrieve email and phone numbers. Use this tool when the user wants the full profile for one specific person — "look up that contact", "tell me about Jane Smith", "get her LinkedIn profile" — typically after search_contacts has narrowed down to a single person. To get someone's email address or phone number, use get_contact_email_and_phone instead (credit-gated; consumes 1 lookup credit per successful enrichment). Provide exactly one identifier: - contact_id_partner_network: the id you got back from search_contacts. - contact_id: internal RealtyAds contact id (e.g. from get_company_decision_makers or get_website_visitors). - linkedin_profile_url: a full LinkedIn URL like "https://www.linkedin.com/in/jane-smith/" or the "linkedin.com/in/jane-smith" tail. Caching: if we have the contact on file and their LinkedIn profile was refreshed in the last 30 days, we return the cached values without re-querying anything. Otherwise we run the full enrichment pipeline (LinkedIn profile ingest + partner-network lookup) before returning. Pass force_refresh: true to skip the cache check and re-run the pipeline regardless of how recently the contact was looked up. Returns one contact: - contact_id, contact_id_partner_network - first_name, last_name, job_title, company_name, linkedin_company_id - linkedin_profile_url, profile_image_url - location, country_code - linkedin_followers, linkedin_connections - data_freshness: { linkedin_profile, contact_info_primary, contact_info_fallback, record_last_updated } as YYYY-MM-DD dates Empty fields are dropped from the response. Do not use this tool to search or filter — that's search_contacts. Do not use it to list the senior decision-makers at a whole company — that's get_company_decision_makers. Do not use it to get email or phone — that's get_contact_email_and_phone.

realtyads_knowledge_query

ChatGPT
Answer a non-client-specific factual question about RealtyAds, its products, services, platform, or knowledge base. Searches the RealtyAds client knowledge base and returns a synthesized answer to the question. Use this tool for any non-client-specific question about RealtyAds, including the company, products, services, packages, platform features, workflows, or client-safe knowledge-base topics. Do not use this tool for account-specific reporting, account performance, website visitors, companies reached, targeting settings, leads, or deal attribution. For those, use account-specific MCP tools. Returns a plain-text answer. The tool handles retrieval and synthesis internally — pass the question as-is, do not pre-format it as a search query.

remove_companies_from_targeting

ChatGPT
Bulk version of remove_company_from_targeting: drop many companies off one RealtyAds account's targeted-firms list in a single call. Each entry identifies one company by targeted_firm_id or linkedin_company_id. Use this tool when the user wants to remove several companies from a RealtyAds account's targeted-firms list in one go — for example after pasting a list of companies they no longer want targeted, or sweeping out tenant pursuits that just executed leases. Per-entry behavior matches remove_company_from_targeting. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - companies: a JSON array (up to 100 entries) where each entry is an object with one of: - targeted_firm_id (preferred), OR - linkedin_company_id Per-entry behavior (same as remove_company_from_targeting): - Soft-deactivates the targeted-firms row. - When a LinkedIn id is known, scrubs the company from r1.linkedin_company_targeting, sets partner-pursuit / partner-integration "block" flags, and INSERTs an exclusion row when none exists. - Logs "Updated Company Targeting" / "Removed a company target" with action_source="mcp". Errors are per-entry, not per-batch. A bad row returns action_taken="failed" with an "error" string for that entry; the rest of the batch still runs. The top-level response carries counts (removed / already_removed / not_targeted / failed) so the caller can see at a glance what happened. This is a WRITE action. Every successful remove is logged under the calling user.

remove_company_from_targeting

ChatGPT
Remove one company from a RealtyAds account's targeted-firms list. Pass targeted_firm_id (preferred) or linkedin_company_id. The portal's Company Targeting modal trash-icon behavior, exposed via MCP. Use this tool when the user wants to stop targeting a specific company on a RealtyAds account — "remove CBRE from targeting", "we should stop targeting Apple", or right after listing companies via get_companies_reached / get_ad_targeting and deciding one shouldn't be on the list. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - one of: - targeted_firm_id: the row id on r1.realty_client_targeted_firms (the id the portal's targeting list uses). Highest priority. - linkedin_company_id: numeric LinkedIn company id. We resolve to the matching active row on the targeted-firms list. What the call does (matches the portal's "remove" flow exactly): - Soft-deactivates the targeted-firms row (sets active=0, not a hard DELETE) so the audit trail of prior targeting stays intact. - When a LinkedIn id is known, scrubs the company from r1.linkedin_company_targeting, flips partner-pursuit / partner-integration "block" flags on for that (account, company) pair, and INSERTs an active exclusion row when one isn't already on file. This last step is what makes "remove" sticky — without it, the next matching cron pass would re-add the company. Returns: - action_taken: "removed", "already_removed" (the row was already inactive), or "not_targeted" (no row found for the supplied identifier) - targeted_firm_id: id of the row that was deactivated - account_id, linkedin_company_id, company_name - added_to_exclusion_list: true when a fresh exclusion was inserted - excluded_firm_id: id of that exclusion row when present - message: short human-readable summary Idempotent: re-removing a company that's already inactive returns action_taken="already_removed" and writes nothing. This is a WRITE action, logged in the portal activity feed under the calling user.

remove_contact_from_targeting

ChatGPT
Remove one contact from a RealtyAds account's targeted-users list and register them as excluded so future matching can't re-add them. The portal's Pursuit Profile 'Remove from targeting' button, exposed via MCP. Use this tool when the user wants to stop targeting a specific contact on a RealtyAds account — "remove Jane Smith from targeting", "stop targeting that contact", or right after listing decision-makers via get_company_decision_makers / get_website_visitors and deciding one shouldn't be on the list. Required: - account_id: the RealtyAds property account. Must be an account the caller has access to. - contact_id: internal RealtyAds contact id (audience_data.contacts.id). This is the same id returned by get_company_decision_makers, lookup_contact_information, lookup_company_information's top_decision_makers block, and the decision_makers block on get_deal_attribution. Optional: - targeting_user_id: the row id on r1.client_targeted_users when the caller already has it (skips the row lookup). Useful when you got this id from add_contact_to_targeting earlier in the conversation. What the call does (matches the portal's "Remove from targeting" flow exactly): - Hard-deletes the contact's row on the targeted-users list. - INSERTs (or UPDATEs) the contact's identity on the exclusion list so the matching cron can't bring them back. - Logs the action in r1.user_change_log under "Updated Decision-Maker Targeting" with description "Removed a decision-maker from targeting". Returns: - action_taken: "removed" (had a targeting row), "added_to_exclusion_only" (contact existed in the directory but wasn't on the targeting list — exclusion still added so future matching can't add them), or "not_found" (contact id not in the directory) - audience_data_contact_id - targeting_user_id_removed: the row id that was hard-deleted (null when the contact wasn't on the targeting list) - excluded_targeting_user_id: the row id on the exclusion list - account_id, first_name, last_name, job_title, company_name - message: short human-readable summary Idempotent: re-removing a contact who's already off the targeting list just refreshes the exclusion row and returns action_taken="added_to_exclusion_only". This is a WRITE action, logged in the portal activity feed under the calling user.

remove_custom_job_title_from_targeting

ChatGPT
Remove one custom job title from the targeted-job-title list for a RealtyAds account. Identify the row by row_id (preferred — surfaced by get_ad_targeting in filters.job_titles) or by LinkedIn title_id. Use this tool when the user wants to drop a job title from RealtyAds AI's job-title targeting — "stop targeting VP of Marketing on this account", "remove Heads of Workplace from our targeting". What it does (mirrors the portal): - DELETEs the matching row from r1.realty_client_targeted_job_titles. - Idempotent: an already-absent row returns action_taken="not_targeted" with no writes. - Logs to r1.user_change_log under "Updated Job Title Targeting" with action_source="mcp". The portal itself does not log this action. Identify the row to remove — provide one of: - row_id: the row id surfaced by get_ad_targeting in filters.job_titles[].row_id (preferred when you've just read state). - title_id: the LinkedIn title id (numeric string, tail of urn:li:title:<n>). If both are supplied, row_id wins. Returns: - action_taken: "removed" or "not_targeted" - row_id, account_id, title_id, title_name - message: short human-readable summary This is a WRITE action.

remove_geography_from_targeting

ChatGPT
Remove one geography from the targeted-geographies list for a RealtyAds account. Identify the row by row_id (preferred — surfaced by get_ad_targeting in geography.submarkets) or by LinkedIn geo_id. Use this tool when the user wants to drop a geography from RealtyAds AI's geographic targeting — "stop targeting White Plains", "remove the Jersey City geography from our list". What it does (mirrors the portal): - DELETEs the matching row from r1.realty_client_targeted_geos. - Idempotent: an already-absent row returns action_taken="not_targeted" with no writes. - Logs to r1.user_change_log under "Updated Geography Targeting" with action_source="mcp". The portal itself does not log this action. Identify the row to remove — provide one of: - row_id: the row id surfaced by get_ad_targeting in geography.submarkets[].row_id (preferred). - geo_id: the LinkedIn geo id (numeric string, tail of urn:li:geo:<n>). If both are supplied, row_id wins. Returns: - action_taken: "removed" or "not_targeted" - row_id, account_id, geo_id, geo_name - message: short human-readable summary This is a WRITE action.

request_quarterly_report

ChatGPT
Generate the same Quarterly Executive Summary PDF the portal produces for one account and one quarter. Full-quarter recap: tenant reach, broker reach, website visitors, overview, and AI optimizations. Use this tool when the user wants the Quarterly Executive Summary — "give me the Q1 executive summary", "download the quarterly report for last quarter", "the quarterly PDF". What it does: - Generates the full-quarter Executive Summary PDF — the same report the Reporting Center produces. - Covers tenant reach, broker reach, website visitors, quarterly overview, and AI optimizations. Inputs: - account_id (required). - quarter (optional): "Q1-2026", "Q2-2026", etc. Defaults to the most recent eligible quarter. Returns a text summary containing the direct download URL (always show it to the user) + a resource_link to the PDF, 24-hour expiry. Gating: only the last four complete calendar quarters are available, and only when all three months in the quarter are active for the account. Returns a clear error if the requested quarter doesn't qualify or no eligible quarter exists.

save_current_ad

ChatGPT
Save the campaign's current live content as a new reusable saved ad. Does not change what is running. Use this only to snapshot what is currently live. It cannot add new copy or media. Use create_new_saved_ad to author new content, or duplicate_saved_ad to copy an existing saved ad.

schedule_ad_rotation

ChatGPT
Set up (or turn off) automatic rotation of saved ads on a campaign — cycle evenly through saved ads on a fixed cadence, or run specific saved ads over explicit date ranges. Sets up (or turns off) automatic rotation of saved ads on a campaign. Set active:false to turn scheduling off. schedule_type is: - "rotate_evenly" (default): cycle through saved_ad_ids every rotation_frequency_days. Needs at least 2 saved ads. rotation_frequency_days must be exactly 7, 14, 21, or 28 (Every Week / Every 2 Weeks / Every 3 Weeks / Every 4 Weeks — the portal scheduler options). - "custom_dates": run specific saved ads over explicit date ranges supplied via custom_dates (each {saved_ad_id, start_date: YYYY-MM-DD, end_date: YYYY-MM-DD}). All saved-ad ids must belong to the campaign. This changes how the client's live ads rotate — confirm with the user first, naming the account and property/campaign. Get campaign_id and saved_ad_ids from get_ad_campaigns. This is a WRITE action.

search_companies

ChatGPT
Search the LinkedIn-backed company directory by name; returns matching companies with their LinkedIn id, profile URL, logo, website, industry, employee count, HQ location, and headcount growth rates when available. Use this tool when the user asks you to find, identify, or disambiguate a company by name — "look up Cushman & Wakefield", "find me companies named Studio", "what's the LinkedIn id for JLL?", or any prelude to a follow-up call (get_company_decision_makers, add_company_to_targeting, etc.) that needs a linkedin_company_id. Backed by two sources combined into one result set: 1. Local r1.linkedin_companies — the ~6M-company directory the portal's targeting wizard searches against, populated by the LinkedIn enrichment crons. Two query plans run in order: FULLTEXT MATCH on company_name (tokens ≥3 letters become +token*), then a prefix LIKE fallback for two- or three-letter tickers like "JLL" or "BXP" that FULLTEXT skips. 2. Live LinkedIn typeahead via ad_manager_linkedin::search_company_by_name — picks up long-tail companies that haven't been enriched into the local cache yet. Cached for 7 days at the API layer. Both sources run on every call and the results are deduplicated by linkedin_company_id (local cache wins where they overlap because it carries the richer enrichment). Each match carries a source field telling the LLM where it came from: "cached" (already in our local LinkedIn cache, fully enriched) or "linkedin_api" (came in from the live LinkedIn typeahead — company_id and company_name are reliable, other fields may be empty until the enrichment crons catch up). Results are ordered by employee_count DESC, then shortest company_name. The canonical entry for a popular company (e.g. the 94k-employee "JLL", company_id 3178) rises above the duplicate vanity entries that LinkedIn allows ("JLL India", "JLL MENA", auto-generated regional pages). Each match returns: - linkedin_company_id, company_name - linkedin_profile_url (built from the LinkedIn universal_name slug) - logo_url, website_url, website_domain, industry, company_size, employee_count - hq_city, hq_state, hq_country_code (when LinkedIn populated them) - employee_growth_rates: { "6_month": "+8%", "12_month": "+12%", "24_month": "+20%" } when cached, omitted when not - cre_tags: ["brokerage"], ["owner"], or both, when our CRE classifiers have flagged the company. Omitted otherwise. - source: "cached" or "linkedin_api" (see above). Filters: - limit (default 25, max 50) - industry (substring match on industry name, e.g. "Real Estate") - min_employees (integer floor on employee_count) - hq_country_code (two-letter ISO code, e.g. "US") - cre_only (boolean — restrict to CRE brokerages or owners we've classified) Do not use this tool to list companies that visited a website (get_website_visitors), companies reached by ads (get_companies_reached), or contacts (search_contacts). It does not include engagement signals — it's a pure directory lookup.

search_contacts

ChatGPT
Find any commercial-real-estate professional anywhere — searches our partner-network contact directory (LinkedIn + ZoomInfo + Apollo) by company, name, job title, function, management level, and location. NOT limited to RealtyAds customers or anyone with a RealtyAds portal account; use for prospecting, lead-gen, and ad-hoc research lookups. Returns matching contacts with a partner-network contact id to feed into lookup_contact_information. Use this tool when the user wants to find or list people by some combination of filters — "give me all the marketing leads at CBRE in Chicago", "who are the VPs of Real Estate at JLL?", "find Jane Smith at Microsoft", "any director at Snowflake in New York?", or as a prelude to add_contact_to_targeting (coming soon). For an unfiltered "top contacts", "key people", or "decision makers" request at one company, prefer get_company_decision_makers. Continue using search_contacts when the user provides explicit name, title, function, seniority, or location filters. At least one filter is required (an unbounded search is rejected). Combine as many as you need to narrow the result: - company_name: company the contact works at, e.g. "CBRE", "JLL". Free-form name match. - linkedin_company_id: numeric LinkedIn company id (alternative to company_name). When supplied, the tool resolves the name from RealtyAds' internal company directory and uses that. - linkedin_company_url: LinkedIn company URL such as "https://www.linkedin.com/company/jll/". Handled like linkedin_company_id when the URL exists in RealtyAds' company directory. - first_name / last_name: name parts. Useful when the user has a person in mind but isn't sure of the company. - job_title: substring match on title, e.g. "VP of Real Estate", "Marketing Director". - job_function: broad department. Partner network only supports a fixed list; common aliases are auto-mapped. Supported: Accounting, Business Development, Construction, Customer Success, Engineering & Technical (alias "Engineering"), Finance, Human Resources (alias "HR"), Information Technology (alias "IT"), Legal, Marketing, Operations, Product Management, Real Estate, Research & Development, Sales. - management_level: seniority filter. Only Director, Manager, Non-Manager (alias "Individual Contributor"), and Board Member are supported on this endpoint — the partner network does NOT expose a C-Level or VP filter on contact search. To filter for C-suite or VP contacts, use job_title with terms like "Chief", "CEO", or "Vice President". - city / state / country: contact location (where the person works, not the company HQ). - zip_code: ZIP / postal code filter (US-style works best). - city: narrows results to a specific city after the search runs (the contact directory does not accept a city input directly, so this is a client-side post-filter — use it alongside at least one of the other filters, not by itself). Optional: - limit: default 10, max 25. Returns: - total_matches: how many contacts match the filters in the underlying directory (may be larger than what's returned). - shown_in_response, has_more. - contacts[]: each entry carries - contact_id_partner_network: a stable id for this contact (string). Feed it back exactly as received — to lookup_contact_information for the person's full profile (and their internal contact_id), or to get_contact_email_and_phone for their email and phone. The directory routes the lookup automatically based on the id's shape. - first_name, last_name (title-cased; last name may come back partially obfuscated on a subset of rows — e.g. "Sm***h" — when the upstream source masks it. lookup_contact_information returns the full name.) - job_title - company_name (the company the contact currently works at) - location ("Chicago, IL") - linkedin_profile_url (when the directory has it) - last_updated (YYYY-MM-DD — when the directory last refreshed this record) - warnings[] when the search couldn't be run as requested (e.g. no filters supplied). Empty per-contact fields are dropped from each entry. Contact info (work email, direct phone) is intentionally N…

set_company_targeting_priority

ChatGPT
Set the targeting priority (Low / Normal / High) on one company in the targeted-firms list for a RealtyAds account. Mirrors the portal company priority dropdown. The "Boost" option is intentionally not supported here — use a dedicated boost tool when added. Use this tool when the user wants to nudge how aggressively a specific targeted company is reached — "make Microsoft a high-priority target", "drop Apple back to normal priority", "deprioritize Salesforce". What it does (mirrors the portal): - UPDATEs the priority_level column on r1.realty_client_targeted_firms. - Conditional activity-feed log: the portal only logs transitions that involve High — Normal↔High and Low↔High. We mirror that exactly: a Normal↔Low change quietly updates without producing a user_change_log row; a transition to/from High logs under "Added High Priority Targeting" or "Removed High Priority Targeting" with action_source="mcp". - Idempotent: setting the priority to its current value returns action_taken="no_changes" with no writes. Required: - account_id: the RealtyAds property account. - targeted_firm_id: row id on r1.realty_client_targeted_firms. Surfaced by get_ad_targeting in target_companies.companies[].targeted_firm_id. - priority: "low", "normal" (or "standard"/"default"), or "high". Returns: - action_taken: "updated", "no_changes", or "not_found" - account_id, targeted_firm_id, company_name - priority, previous_priority - logged_in_activity_feed: true when the change crossed the High threshold - message: short human-readable summary Use add_company_to_targeting to ADD a company. This tool only changes priority on a company already on the list. The portal Boost flow (priority=100, which is a sticky escalation tied to realty_account_boosts) is intentionally NOT exposed here — a separate boost tool will be added later. This is a WRITE action.

set_contact_targeting_priority

ChatGPT
Set the targeting priority (Low / Normal / High) on one contact in the targeted-individuals list for a RealtyAds account. Mirrors the portal's broker-reach priority dropdown. Use this tool when the user wants to nudge how aggressively a specific targeted contact is reached — "make Jane Smith a high-priority contact", "drop John Doe back to normal", "deprioritize this broker". What it does (mirrors the portal): - UPDATEs the priority_level column on r1.client_targeted_users. - Always logs to r1.user_change_log under "Updated Broker Priority Targeting" with action_source="mcp", on every successful change. (Portal also always logs this — even Normal↔Low transitions, unlike company priority.) - Idempotent: setting the priority to its current value returns action_taken="no_changes" with no writes. Required: - account_id: the RealtyAds property account. - targeting_user_id: row id on r1.client_targeted_users. Surfaced by get_ad_targeting in target_individuals.individuals[].targeting_user_id. - priority: "low", "normal" (or "standard"/"default"), or "high". Returns: - action_taken: "updated", "no_changes", or "not_found" - account_id, targeting_user_id, contact_name - priority, previous_priority - message: short human-readable summary Note: the portal has no Boost option for contacts (only for companies). This tool accepts only low/normal/high. This is a WRITE action.

submit_feedback

ChatGPT
Submit product feedback, a suggestion, or an issue report to the RealtyAds team. Use this tool only when the user explicitly wants to send feedback to RealtyAds, report a product issue, or suggest an improvement. Required: - feedback_message: the feedback in the user's own words. Optional: - account_id: the RealtyAds account the feedback concerns. Include it when known so the correct Account Manager is notified. - sentiment: "positive" or "negative". - feedback_source: the product area or feature the feedback concerns. - page_url: the relevant RealtyAds page, when the user supplied one. This is a WRITE action. Submit once per user request and do not claim that it creates a support ticket or guarantees a response.

unexclude_company_from_targeting

ChatGPT
Remove one company from a RealtyAds account's exclusion list so the company is no longer permanently blocked from being targeted. Inverse of exclude_company_from_targeting. Does not by itself put the company onto the active targeting list — use add_company_to_targeting for that. Use this tool when the user wants to un-block a company that was previously excluded from targeting on a RealtyAds account — "we excluded Apple by mistake, can you put them back on the table?" or "Microsoft was excluded, but we'd like RealtyAds AI to be able to target them again". What it does (mirrors the portal): - Removes the company from the account's exclusion list (soft-delete; active=0). - Clears any partner-pursuit / partner-integration "block" flags for the (account, company) pair, so pursuit signals can promote the company onto ads again. - Does NOT add the company to the active targeting list. If the user wants the company actively targeted, follow up with add_company_to_targeting. Identifying the row to unexclude — provide one of: - excluded_firm_id: the row id on the exclusion list (returned by get_ad_targeting in the excluded_companies block, or from a prior exclude action). - linkedin_company_id: numeric LinkedIn company id (use search_companies or lookup_company_information to resolve a name first). If both are supplied, excluded_firm_id wins. Returns: - action_taken: "unexcluded" or "not_excluded" - excluded_firm_id, account_id, linkedin_company_id, company_name - message: short human-readable summary Idempotent: an already-inactive (or absent) exclusion row returns action_taken="not_excluded" with no writes. This is a WRITE action. Logged in r1.user_change_log under the calling user with the same "Updated Company Exclusion" action label exclude uses, plus action_source="mcp".

update_company_size_targeting

ChatGPT
Set which employee-headcount buckets RealtyAds will target for a property account. Replace-all semantics — send the full desired set of buckets. Pass an empty list (or just 'No Restrictions') to clear the size filter so ads target companies of any size. Use this tool when the user wants to filter targeting by company headcount — "only target companies with 200+ employees", "focus on mid-size: 51 to 500 employees", "drop the very-small bucket", "open targeting back up to all sizes". What it does (mirrors the portal): - UPSERTs one row on r1.realty_account_target_company_size with a comma-separated list of bucket labels. - Inserts a targeting_update_log row of type update_company_size_targeting. - Logs to r1.user_change_log under "Updated Company Size Targeting" with action_source="mcp". Replace-all semantics. Send the full desired set of buckets. Accepted bucket values (case-insensitive, "Employees" suffix optional, "to" instead of "-" also accepted): - "No Restrictions" → target companies of any size (clears the filter) - "2-10 Employees" - "11-50 Employees" - "51-200 Employees" - "201-500 Employees" - "501-1000 Employees" - "1001-5000 Employees" - "5001-10000 Employees" - "10000+ Employees" Special handling: - An empty list or a list containing only "No Restrictions" resolves to a single "No Restrictions" entry — the portal's default unfiltered state. - If "No Restrictions" is included alongside concrete buckets, "No Restrictions" is dropped (concrete buckets win, mirrors the UI checkbox behavior). - Unrecognized inputs are dropped with a warning, not written. Returns: - action_taken: "updated" or "no_changes" - account_id - company_size_buckets: the new full list of canonical bucket labels in ascending order - warnings: any inputs that couldn't be mapped to a known bucket - message: short summary This is a WRITE action.

update_growth_signal_targeting

ChatGPT
Turn the RealtyAds account's growth-signal targeting toggles on or off — Power Prospecting (hiring growth), Coworking, Funding events, and Site Selectors. Each is a boolean. Partial input is safe — missing toggles keep their current value. Use this tool when the user wants to enable or disable any of the RealtyAds growth-signal targeting features on a property account — "turn on power prospecting", "stop targeting based on funding events", "enable site selectors targeting on this property", "disable coworking targeting". These four toggles live together on a single portal settings card. Each is independent (you can flip any combination), but they share the same save endpoint and are typically updated together. Toggles: - power_prospecting — RealtyAds AI promotes companies with strong hiring growth signals (e.g. 20%+ headcount rise). - coworking_targeting — RealtyAds AI promotes coworking companies / flexible-space tenants. - funding_targeting — RealtyAds AI promotes companies with recent funding events. - site_selectors_targeting — RealtyAds AI promotes site-selection / corporate-real-estate-advisory firms. What it does (mirrors the portal): - UPDATEs the corresponding column on r1.realty_accounts per changed toggle (power_prospecting maps to company_growth_targeting; others map to columns of the same name). - INSERTs one targeting_update_log row per changed toggle. - Logs one r1.user_change_log entry per change ("Updated Power Prospecting Targeting", "Updated Funding Targeting", etc.) with action_source="mcp". Partial input is safe. Send only the toggles you want to change — missing keys retain their current value. To disable a toggle, set it to false explicitly. Read current state from get_ad_targeting.growth_signals before calling so the user-visible change is intentional. Returns: - action_taken: "updated" or "no_changes" - account_id - growth_signals: the new full state — { power_prospecting, coworking_targeting, funding_targeting, site_selectors_targeting } - changes: per-toggle diff - warnings: any unrecognized toggle names you sent - message: short summary This is a WRITE action.

update_industry_targeting

ChatGPT
Replace the account's industry targeting list with a new set of broad industry ids and/or detailed-category ids. Replace-all semantics — send the full desired set. Read current state from get_ad_targeting.filters.industries / .detailed_categories before calling. Use this tool when the user wants to change the industry focus on a RealtyAds property account — "target Technology and Financial only", "add Life Sciences to our industry focus", "drop Retail from targeting", "broaden to include Manufacturing". Replace-all semantics. Send the full desired set of industry ids in one call. The tool DELETEs the existing industry junction rows and re-INSERTs the new set. Two kinds of ids are accepted: - Broad industries (ids 1–49) — these also write to the legacy CSV column r1.realty_accounts.target_industries. - Detailed industry categories (ids in the thousands) — these are sub-segments within a broad industry (e.g. "Accounting Services" under "Business Services"). They only write to the junction. Common broad industry ids (active in r1.realty_industries): 1 = Business Services 2 = Education 3 = Energy Sector 4 = Financial Sector 5 = Legal Sector 6 = Life Sciences 7 = Manufacturing 8 = Media & Communications 9 = Medical Practice 10 = Nonprofit 11 = Public Sector 12 = Commercial Real Estate 13 = Retail & Consumer Goods 14 = Technology 15 = Transportation & Logistics 16 = Warehousing If the caller doesn't know an id, call get_ad_targeting first to see the current set, or look up by name via the read-side knowledge tools — this tool will not accept industry names. Validation: every supplied id is checked against the two catalog tables. Unrecognized ids are dropped with a warning, not silently committed. Returns: - action_taken: "updated" or "no_changes" - account_id - industries: the new full set — array of { industry_id, name, kind: "broad" | "detailed" } - added: array of { industry_id, name } added vs prior state - removed: array of { industry_id, name } dropped vs prior state - warnings: any unrecognized ids you sent - message: short summary This is a WRITE action. Logged in r1.user_change_log under "Updated Industry Targeting" with action_source="mcp".

update_landing_page

ChatGPT
Update the account-level landing page URL after checking that the link is reachable. Change where ads click through (new URL, or use_realtyads_lander=true for the RealtyAds lander). Call get_landing_page first. Confirm with the user, then pass confirm=true. Rejects URL shorteners, domains that do not resolve, and pages that time out or respond 400/401/403/404/500 — returns error=bad_link and writes nothing. PDF links are rehosted on realtyads.com, so the saved URL may differ from the input.

update_saved_ad

ChatGPT
Edit an existing saved ad's copy, media, landing page, or label in place without making it live. Omitted fields stay unchanged. Use media ids returned by list_media_library or add_media_to_library. This keeps the same saved_ad_id and does not change the live ad. Call launch_saved_ad separately after explicit user confirmation.

update_target_audience_groups_targeting

ChatGPT
Turn the account's high-level RealtyAds target audiences on or off — broker reach, tenants in the market, asset managers, residential brokers (multi-family), and the bookkeeping entries for the account's own uploaded lists. Each audience is a boolean. Read current state from get_ad_targeting.target_audiences before calling. Use this tool when the user wants to change which high-level RealtyAds proprietary audiences a property account is targeting — "turn off broker reach", "stop targeting tenants in the market on this account", "turn the asset managers audience back on", etc. This is the same panel the portal's "Target Groups" section saves. What it does (mirrors the portal): - Replaces the prioritized_targeting_groups JSON on r1.realty_accounts with the desired full state. - Inserts a targeting_update_log row of type targeted_group_update. - Logs to r1.user_change_log under "Updated Targeting Groups" with action_source="mcp". How to call it: - account_id: the RealtyAds property account. - target_audiences: a JSON object keyed by audience group id, with a boolean value for each. Group ids: 1 = Commercial Real Estate Brokers (drives "Broker Reach") 2 = Tenants In The Market (drives "Tenant Reach") 3 = My Uploaded Target Individual List 4 = My Uploaded Target Company List 5 = Asset Managers & Property Owners 6 = Residential Real Estate Brokers (multi-family accounts only) Example: { "1": true, "2": true, "5": false } sets Brokers + Tenants on, Asset Managers off, leaves anything else at its current state. Partial input is safe: any group id not in your request keeps its existing value. To explicitly opt a group out, set its key to false. Group ids that don't exist or aren't visible to this account's category (e.g. group 6 on a non-multi-family account) are dropped with a warning, not written. Read current state first via get_ad_targeting (the target_audiences block lists each group's name + included bool). Treat the result as the source of truth before modifying. Returns: - action_taken: "updated" or "no_changes" - account_id - target_audiences: the new full state — array of { group_id, name, included } - changes: per-group diff vs prior state - warnings: any unrecognized group ids you sent - message: short summary This is a WRITE action.

update_vertical_targeting

ChatGPT
Turn the RealtyAds account's vertical / specialty targeting toggles on or off — geo-fencing (universal), live-work-play and ideal-tenant (Multi-Family only), life-science investor targeting (Life Science only). Each is a boolean. Partial input is safe — missing toggles keep their current value. Use this tool when the user wants to enable or disable any of the RealtyAds vertical / specialty targeting features on a property account — "turn on geo-fencing for this property", "stop live-work-play targeting", "enable ideal-tenant targeting on this multi-family asset", "turn on life-science investor targeting". These toggles live on the same portal settings card as the growth signals; this tool covers the vertical / specialty ones, while update_growth_signal_targeting covers the universal behavior signals (power_prospecting, coworking, funding, site_selectors). Two tools so the args stay focused and the LLM can reason about which group of toggles a request matches. Toggles: - geo_fencing — universal. Enables physical-location geo-fencing of the property's defined geo radius. - live_work_play_targeting — Multi-Family only. Promotes live/work/play lifestyle audience signals. - ideal_tenant_targeting — Multi-Family only. Promotes the property's ideal-tenant profile. - life_science_investor_targeting — Life Science only. Promotes life-science investor signals. What it does (mirrors the portal): - UPDATEs the corresponding column on r1.realty_accounts per changed toggle (geo_fencing stores 'enable' / 'disable'; the other three store '1' / '0'). - INSERTs one targeting_update_log row per changed toggle. - Logs one r1.user_change_log entry per change ("Updated Geofencing", "Updated Live Work Play Targeting", "Updated Multi-family Ideal Tenant Targeting", "Updated Life Science Investor Targeting") with action_source="mcp". Partial input is safe. Send only the toggles you want to change — missing keys retain their current value. To disable a toggle, set it to false explicitly. The vertical-specific toggles (live_work_play, ideal_tenant, life_science_investor) only have meaningful behavior on accounts of the matching vertical — they're stored unconditionally either way, mirroring the portal. Read current state from get_ad_targeting before calling (geo_fencing is exposed under geography.geo_fencing; the vertical-specific bools are under vertical_specific). Returns: - action_taken: "updated" or "no_changes" - account_id - vertical_targeting: the new full state — { geo_fencing, life_science_investor_targeting, live_work_play_targeting, ideal_tenant_targeting } - changes: per-toggle diff - warnings: any unrecognized toggle names you sent - message: short summary This is a WRITE action.

Capabilities

Writes

Example Prompts

Click any prompt to copy it.

App Stats

66

Tools

3

Prompts

ChatGPT

Platforms

Category

Home Search

Works with

ChatGPT

Data refreshed daily