This is the full developer documentation for iSearchFrom # iSearchFrom Documentation > Learn how to search, track, and monitor visibility from the markets your customers actually see. iSearchFrom helps teams check search results from specific countries, cities, devices, languages, and search settings — then turn those checks into ongoing tracking. Run one-off searches, monitor organic rankings, watch paid ads, and measure how your brand shows up across AI answers. Start here [1Quick Start](/guides/quickstart/) [Run your first useful search workflow end to end in a few minutes.](/guides/quickstart/) [2Choose the Right Tool](/guides/choose-tool/) [Match each job — quick check, tracking, AI monitoring — to the right area.](/guides/choose-tool/) [3Plans, Tokens & Limits](/guides/plans-tokens-limits/) [Understand what drives usage so there are no surprises on billing.](/guides/plans-tokens-limits/) Explore the tools [Direct Search](/search-tools/direct-search/) [Build localized Google or Bing URLs for fast, manual one-off checks.](/search-tools/direct-search/) [Search Engine Proxy](/search-tools/search-engine-proxy/) [Capture full search result pages exactly as a local user would see them.](/search-tools/search-engine-proxy/) [SERP Position Tracker](/tracking/serp-position-tracker/) [Monitor organic rankings for your keywords over time, by location.](/tracking/serp-position-tracker/) [Ads Position Tracker](/tracking/ads-position-tracker/) [Track paid placements and competitor ads across the markets you target.](/tracking/ads-position-tracker/) [AI Visibility](/ai-visibility/overview/) [Measure brand mentions, recommendations, and share of voice across AI platforms.](/ai-visibility/overview/) [Projects & History](/search-tools/projects/) [Group saved searches, revisit past runs, and keep research organized.](/search-tools/projects/) [Open the app](https://app.isearchfrom.com/) [Sign in, choose the active workspace, and start from the product sidebar.](https://app.isearchfrom.com/) [Build with the API](/reference/api/) [Automate searches and pull results into your own systems with the REST API.](/reference/api/) [Need a hand?](/help/faq/) [Browse the FAQ and troubleshooting, or get in touch with support.](/help/faq/) # AI Visibility Alerts > Configure alert conditions for brand visibility and competitor activity in AI answers. AI Visibility alerts highlight meaningful movement in AI responses. Use them to monitor important changes without manually reviewing every check. ## Alert categories [Section titled “Alert categories”](#alert-categories) AI Visibility alerts can cover: * visibility appeared * visibility lost * visibility rose * visibility fell * new sponsored placement * sponsored placement stopped * competitor sponsored activity The exact alert titles shown in the app depend on the detected event. ## Visibility alerts [Section titled “Visibility alerts”](#visibility-alerts) Visibility alerts are useful when your brand begins or stops appearing, or when the visibility percentage changes materially between runs. A visibility alert compares the last three runs against the three before them and needs both a material swing (15 percentage points) and a swing larger than sampling noise would explain at your campaign’s size. Every check is one sample of a non-deterministic answer, so a campaign with a handful of prompts alerts only on dramatic moves, while a campaign with many prompts and platforms alerts on modest ones. Runs where most checks failed are skipped rather than read as lost visibility. Use visibility alerts for: * product launches * brand campaigns * high-intent category prompts * important markets * competitor comparison prompts ## Competitor alerts [Section titled “Competitor alerts”](#competitor-alerts) Competitor alerts fire when a competitor starts running a sponsored placement that was not there in the previous window. A competitor who has advertised all along is the campaign’s baseline and is not announced again after every run. ## Notification channels [Section titled “Notification channels”](#notification-channels) Every alert appears in the campaign’s alert feed and, when the campaign has a webhook URL, is posted to it immediately. Organization owners receive alerts in the app and by email by default; admins and members opt in under **My alerts**. Alert emails are batched to at most one per hour per organization and grouped by campaign. Each person can switch alert emails off in their notification preferences, and a campaign can be kept out of the email with its “Email alerts” setting. On plans with alert channels, alerts can also go to Slack, Microsoft Teams, Google Chat, Discord, Telegram or a webhook destination — see [Alert Channels](/tracking/alert-channels/). Use email for human review. Use webhooks when alerts need to enter another system. See [Schedules and Alerts](/tracking/schedules-alerts/) for how alerts are evaluated. ## Avoid alert fatigue [Section titled “Avoid alert fatigue”](#avoid-alert-fatigue) Start with a small number of alert conditions. Add more after you know which movements matter. Good alert setups are: * tied to a specific owner * tied to a decision * limited to important prompts and markets * reviewed regularly ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Open the campaign from the AI Visibility list, then review its alert settings and recent alerts. Use Notifications for account-wide alert history. ## Related docs [Section titled “Related docs”](#related-docs) * [Schedules and Alerts](/tracking/schedules-alerts/) * [Webhooks](/reference/webhooks/) * [Metrics and Results](/ai-visibility/metrics-results/) # Create an AI Visibility Campaign > Set up brand visibility monitoring across prompts, locations, and AI platforms. An AI Visibility campaign defines what brand you track, which competitors you compare against, which prompts you test, where you test them, and how often the checks run. ## Before you start [Section titled “Before you start”](#before-you-start) Brand context Prepare your primary domain, brand name variations, and industry. Competitor context Prepare competitor domains or recognizable competitor brand names. Prompt set Prepare 5 to 10 important buying, comparison, recommendation, or category prompts. Run scope Prepare target countries, platforms you care about, and reporting frequency. Prompt starting point If you are unsure which prompts to use, start with buying-intent and comparison prompts. You can add more later. ## Create the campaign [Section titled “Create the campaign”](#create-the-campaign) ### Open the campaign form [Section titled “Open the campaign form”](#open-the-campaign-form) Open **AI Visibility** and choose **New AI Visibility Campaign**. ### Choose project and identity [Section titled “Choose project and identity”](#choose-project-and-identity) Select a project, enter a campaign name, add your primary domain, and choose an industry. ### Add brand names [Section titled “Add brand names”](#add-brand-names) Add the names customers might use for your company or product, including common variations. ### Add competitors [Section titled “Add competitors”](#add-competitors) Add competitors that your customers would reasonably consider. ### Add prompts [Section titled “Add prompts”](#add-prompts) Add specific prompts that reveal buying behavior, comparison behavior, or recommendation behavior. ### Choose locations and platforms [Section titled “Choose locations and platforms”](#choose-locations-and-platforms) Choose the countries and AI platforms that matter to your audience. A country can be narrowed to one or more cities, and each location is asked separately, so a campaign that tracks Sydney and Brisbane gets a Sydney answer and a Brisbane answer. ### Configure alerts and schedule [Section titled “Configure alerts and schedule”](#configure-alerts-and-schedule) Configure alerts, choose a schedule, review estimated usage, and create the campaign. ## Campaign inputs [Section titled “Campaign inputs”](#campaign-inputs) * Brand names Add the names customers might use for your company or product. Include common variations, but avoid adding unrelated products unless they belong in the same reporting view. ```text Acme Acme Analytics Acme.io ``` * Competitors Competitors help calculate share of voice and identify comparison gaps. Add competitors that your customers would reasonably consider. Use domains or recognizable brand names consistently. * Prompts Strong prompts are specific enough to reveal buying behavior: * `What is the best rank tracking software for agencies?` * `Compare tools for local SEO reporting.` * `Which platforms help monitor search visibility across countries?` * `What are the best alternatives to [competitor]?` * Locations and platforms Locations affect platform availability. Some AI platforms may not be available in every country. If a selected location excludes a selected platform, iSearchFrom will restrict incompatible combinations. **Cities.** Tick a country, then add cities from the catalog under it. A country with cities means those cities; a country without means the whole country. Each city counts as one location. ChatGPT, Google AI Mode and Google AI Overview are asked from the city (ChatGPT from its exact coordinate); Perplexity and Gemini can only be asked from a country, so for those two a city is answered at country level. The wizard says so when it applies. Either way the answer is recorded under the city you asked from, so results stay comparable across platforms. The API also accepts a coordinate (`lat`/`lon`) on a location for pin-level targeting on the platforms that support it. ## Schedule and size [Section titled “Schedule and size”](#schedule-and-size) Start weekly unless you need high-frequency monitoring. AI answers can change, but daily checks are not always necessary for early campaigns. 500 checks per run AI Visibility campaigns are capped at 500 checks per run. Checks per run are calculated as `prompts x locations x platforms`. If your campaign is too large, reduce prompts, locations, or platforms, or split the setup into focused campaigns. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Create the campaign from this page after choosing the active organization and project. ## Related docs [Section titled “Related docs”](#related-docs) [Prompts, Brands, and Competitors](/ai-visibility/prompts-brands-competitors/)Choose inputs that produce useful signal. [Metrics and Results](/ai-visibility/metrics-results/)Understand mentions, recommendations, share of voice, and response details. [Plans, Tokens, and Limits](/guides/plans-tokens-limits/)Review check limits and token usage before expanding a campaign. # AI Visibility Metrics and Results > Interpret AI mentions, recommendations, share of voice, sentiment, attributes, and raw responses. AI Visibility results show how your brand and competitors appear across prompts, locations, and AI platforms. Use the metrics to find patterns, then inspect individual checks for context. ## Key metrics [Section titled “Key metrics”](#key-metrics) | Metric | Meaning | | ------------------- | ------------------------------------------------------------------ | | Visibility rate | Percentage of checks where your brand appeared | | Mentions | Number of detected brand mentions | | Recommendation rate | How often your brand was recommended | | Recommended rank | Where your brand appeared in a recommendation list | | Share of voice | Your mention share compared with tracked competitors | | Sentiment | How positively the answer described your brand, from -1 to +1 | | Top pick rate | How often your brand was the leading recommendation | | Attribute gaps | Attributes competitors are mentioned for but your brand is missing | ### Reading sentiment [Section titled “Reading sentiment”](#reading-sentiment) Sentiment runs from **-1 to +1** and is judged in the context of the question that was asked, not as general brand reputation. Being named as the best answer to “best CRM for small teams” scores higher than the same brand mentioned in passing. | Score | What it usually means | | ------------- | ------------------------------------------------------------------------ | | **1.0** | Singled out as the best choice, in emphatic terms | | **0.7 – 0.9** | Clearly recommended, with explicit praise | | **0.4 – 0.6** | Listed favourably, in plain descriptive terms | | **0.1 – 0.3** | Mentioned with reservations, or only in passing | | **0.0** | Named with no evaluative language — for example, the tool being replaced | | **Below 0** | Explicitly criticised | Most brands in a recommendation list land between 0.3 and 0.8, so a move from 0.5 to 0.7 across a campaign is a meaningful shift rather than noise. Track the trend rather than any single check — one answer’s wording varies, but the direction across many checks is the signal. ## Overview tab [Section titled “Overview tab”](#overview-tab) Use the overview to answer: * Is our brand visible? * Is visibility improving? * Which prompts produced mentions? * Which platforms mention us? * Are competitors getting more share? * Are there recent alerts? ## Analytics tab [Section titled “Analytics tab”](#analytics-tab) Use analytics to compare: * visibility over time * share of voice over time * platform performance * location performance * prompt-level visibility * competitor mention share * ad or sponsored mention activity ## Checks tab [Section titled “Checks tab”](#checks-tab) Checks show individual prompt/platform/location outputs. Open a check when you need to understand why a metric changed. Review: * prompt * platform * location * whether your brand was mentioned * whether your brand was recommended * competitor mentions * sponsored status * raw response ## Raw responses [Section titled “Raw responses”](#raw-responses) Raw responses are important because AI answers can be nuanced. Use them to verify whether a detected mention is meaningful, neutral, negative, or incidental. Do not rely only on aggregate metrics for high-stakes decisions. ## Attribute gaps [Section titled “Attribute gaps”](#attribute-gaps) Attribute gaps show what competitors are associated with and where your brand may be absent. Use these gaps for content, positioning, product marketing, and sales enablement ideas. Example: If competitors are repeatedly mentioned for `enterprise reporting` and your brand is missing from those same prompts, you may need better content or positioning around enterprise reporting. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Open a campaign detail page from the list to review Overview, Analytics, Checks, and raw responses. ## Related docs [Section titled “Related docs”](#related-docs) * [AI Visibility Overview](/ai-visibility/overview/) * [Prompts, Brands, and Competitors](/ai-visibility/prompts-brands-competitors/) * [AI Visibility Alerts](/ai-visibility/alerts/) # AI Visibility Overview > Track how your brand appears across AI answer surfaces. AI Visibility helps you monitor how AI platforms mention, compare, recommend, or omit your brand as customers move from traditional search results to generated answers. Beta workflow AI Visibility is currently a beta workflow. Treat results as directional intelligence and review raw responses when a decision matters. ## What it tracks beta [Section titled “What it tracks ”](#what-it-tracks-) Brand and competitor mentions Track whether your brand and selected competitors appear in AI responses. Recommendation rank See whether your brand is recommended and where it appears relative to competitors. Share of voice Compare how often your brand appears across prompts, platforms, and locations. Sentiment and attributes Review tone, strengths, missing attributes, and gaps in generated answers. Prompt-level visibility Understand which prompts cause your brand or competitors to win. Platform and location differences Compare behavior across ChatGPT, Google AI Overviews, Google AI Mode, Gemini, and Perplexity where available. ## When to use AI Visibility [Section titled “When to use AI Visibility”](#when-to-use-ai-visibility) * Brand questions Use AI Visibility to answer whether your brand appears, whether it is recommended, which attributes AI systems associate with it, and where it is omitted. * Competitor questions Use it to see which competitors appear more often, which prompts cause competitors to win, and whether competitor-sponsored mentions or placements are appearing. * Not for SERP rank Use [SERP Position Tracker](/tracking/serp-position-tracker/) for traditional organic rankings and [Ads Position Tracker](/tracking/ads-position-tracker/) for paid search ad placements. ## How campaigns work [Section titled “How campaigns work”](#how-campaigns-work) An AI Visibility campaign combines your project, primary domain, brand names, industry, competitors, prompts, locations, AI platforms, alerts, and schedule. Each prompt, location, and platform combination becomes a check during a run: ```text 10 prompts x 3 locations x 2 platforms = 60 checks per run ``` The current maximum is 500 checks per run. ## Recommended first campaign [Section titled “Recommended first campaign”](#recommended-first-campaign) ### Choose one project [Section titled “Choose one project”](#choose-one-project) Keep the first campaign tied to a single product, market, client, or launch. ### Add your brand context [Section titled “Add your brand context”](#add-your-brand-context) Add your main domain, primary brand name, and common name variations. ### Add high-intent prompts [Section titled “Add high-intent prompts”](#add-high-intent-prompts) Start with 3 to 10 prompts that resemble real buying, comparison, or recommendation questions. ### Add competitors and locations [Section titled “Add competitors and locations”](#add-competitors-and-locations) Add 1 to 3 competitors and 1 or 2 priority locations. ### Choose platforms and schedule [Section titled “Choose platforms and schedule”](#choose-platforms-and-schedule) Choose the platforms your audience uses most and schedule weekly until the signal is proven. ### Review before expanding [Section titled “Review before expanding”](#review-before-expanding) Review the first results, raw responses, mentions, recommendations, and competitor behavior before adding more prompts or markets. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Start here when you want to monitor how your brand appears in AI answers. ## Related docs [Section titled “Related docs”](#related-docs) [Create an AI Visibility Campaign](/ai-visibility/create-campaign/)Set up brand visibility monitoring across prompts, locations, and AI platforms. [Prompts, Brands, and Competitors](/ai-visibility/prompts-brands-competitors/)Choose inputs that produce useful signal. [Metrics and Results](/ai-visibility/metrics-results/)Read mentions, recommendations, share of voice, and response details. # Prompts, Brands, and Competitors > Choose AI Visibility inputs that produce useful signal. AI Visibility is only as useful as the prompts, brand names, and competitors you choose. Treat campaign setup like research design. ## Strong prompt patterns [Section titled “Strong prompt patterns”](#strong-prompt-patterns) Best tools for a job Use category searches that resemble active evaluation. Named comparisons Compare your brand with known competitors. Alternatives to a brand Capture switching and replacement behavior. Role recommendations Ask for recommendations for a role, company type, or use case. Local or regional recommendations Test whether location changes generated recommendations. Problem and solution questions Ask about the underlying problem customers are trying to solve. ## Examples [Section titled “Examples”](#examples) ```text Best tools for tracking Google rankings across countries. Compare Acme and ExampleCo for agency SEO reporting. What software should a marketplace team use to monitor search visibility? Best alternatives to ExampleCo for PPC competitor monitoring. ``` ## What to avoid [Section titled “What to avoid”](#what-to-avoid) * Too vague Avoid prompts like `SEO`, `ranking`, or `What is marketing?` because they rarely produce consistent buying signal. * Too biased Avoid prompts like `Is Acme the best?` because they test leading language instead of customer behavior. * Too internal Avoid prompts like `Tell me about our brand` unless customers actually search that way. ## Brand names [Section titled “Brand names”](#brand-names) Add names that could appear in an AI response: Company name The primary name customers use. Product name Product names that may be mentioned independently. Domain-style name Names such as `Acme.io` when customers use them. Common abbreviation Short names or acronyms customers actually recognize. Old brand name Include only if customers still use it. Do not add every product line unless they should be counted together. ## Competitors [Section titled “Competitors”](#competitors) Competitor lists should reflect actual alternatives: Direct competitors Products customers evaluate side by side. Category leaders Brands likely to appear even when you do not sell against them daily. Regional competitors Local alternatives that matter in specific markets. Incumbent tools Tools customers mention during sales or support conversations. Start small A smaller competitor list makes share of voice easier to interpret. Add more competitors after the first runs show useful signal. ## Prompt grouping [Section titled “Prompt grouping”](#prompt-grouping) Use separate campaigns when prompts have different intent: Brand comparison Named competitor or alternative prompts. Category discovery Best-tool and recommendation prompts. Local recommendations Market-specific or regional recommendation prompts. Enterprise buying Security, procurement, compliance, or team-size prompts. Separate campaigns make results easier to read and reduce noisy averages. ## Use suggestions carefully [Section titled “Use suggestions carefully”](#use-suggestions-carefully) Generated prompt and competitor suggestions can help brainstorming, but review them before saving. Remove prompts that are not relevant to your market or buying motion. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Use these rules while creating or editing an AI Visibility campaign. ## Related docs [Section titled “Related docs”](#related-docs) [Create an AI Visibility Campaign](/ai-visibility/create-campaign/)Set up campaign scope, locations, platforms, prompts, and schedules. [Metrics and Results](/ai-visibility/metrics-results/)Understand how prompt and brand choices affect reported results. [AI Brand Visibility Monitoring](/use-cases/ai-brand-visibility-monitoring/)Apply AI Visibility to brand and competitor monitoring workflows. # Invoices and Payments > Review payment history, invoices, subscription history, and billing status. The billing area includes payment and subscription records for the active organization. ## Payment history [Section titled “Payment history”](#payment-history) Payment history can show: * date * description * amount * status * invoice link when available Common payment statuses include pending, paid, failed, and refunded. ## Subscription history [Section titled “Subscription history”](#subscription-history) Subscription history can show: * plan * usage * billing period * renewal information * status changes Use subscription history when auditing plan changes or reconciling organization access. ## Open an invoice [Section titled “Open an invoice”](#open-an-invoice) If an invoice link is available, open it from payment history on [Billing](https://app.isearchfrom.com/billing). Invoices are handled through the billing provider. ## Failed payments [Section titled “Failed payments”](#failed-payments) If a payment fails: 1. Open [Billing](https://app.isearchfrom.com/billing). 2. Check subscription status. 3. Update payment method if needed. 4. Retry through the billing portal or checkout flow. 5. Contact support if the subscription remains past due. Do not send card numbers to support. For billing investigation, include organization name, approximate date, amount, and the status shown in Billing. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app. Billing shows payment history, subscription history, invoice links, plan details, and the Stripe billing portal button when a subscription is active. ## Related docs [Section titled “Related docs”](#related-docs) * [Manage Subscription](/billing/manage-subscription/) * [Token Usage](/billing/token-usage/) * [Contact Support](/help/contact-support/) # Manage Subscription > Manage organization plan, billing cadence, checkout, and subscription status. Subscription settings live at the organization level. Confirm the correct organization is active before making billing changes. ## What you can manage [Section titled “What you can manage”](#what-you-can-manage) The billing page can show: * current plan * monthly or yearly cadence * tokens remaining * extra tokens * next billing date * payment history * subscription history * available plans * token top-ups * Stripe checkout or portal actions ## Change plan [Section titled “Change plan”](#change-plan) 1. Open [Billing](https://app.isearchfrom.com/billing). 2. Confirm the correct organization is active. 3. Review the current plan and next billing date. 4. Compare available plans. 5. Choose monthly or yearly billing where available. 6. Select the new plan. 7. Review checkout details. 8. Confirm payment through Stripe. Plan changes can affect project limits, member limits, token allocation, API access, exports, SSO, and feature availability. Subscription tokens refresh to the new plan allocation when the subscription change is provisioned. If your organization also has top-up tokens, those remain separate as permanent tokens. ## Manage payment details [Section titled “Manage payment details”](#manage-payment-details) Payment details are handled by Stripe. iSearchFrom does not store raw card details. Use **Manage subscription** when you need to update billing information, review subscription status, or make changes handled through the billing portal. The **Manage subscription** button appears on Billing when the active organization has an active subscription. ## Subscription statuses [Section titled “Subscription statuses”](#subscription-statuses) Common statuses include: * active * trialing * past due * canceled * unpaid If a subscription is past due or unpaid, feature access may be affected until payment is resolved. ## Enterprise plans [Section titled “Enterprise plans”](#enterprise-plans) Contact support if your team needs: * custom token limits * dedicated support * larger member limits * SSO * volume discounts * custom workflows ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app. Use Billing to change plans, manage payment details, open the Stripe portal, and review subscription status for the active organization. ## Related docs [Section titled “Related docs”](#related-docs) * [Token Usage](/billing/token-usage/) * [Token Top-ups](/billing/token-top-ups/) * [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) # Token Top-ups > Buy extra tokens when your organization needs more searches or checks. Token top-ups add extra usage capacity to a organization. Use them when your team needs more proxy searches, tracker runs, or AI Visibility checks before the next subscription renewal. Top-up tokens are permanent organization tokens. They do not reset at renewal, and the app spends subscription tokens first before using top-up tokens. ## When to buy a top-up [Section titled “When to buy a top-up”](#when-to-buy-a-top-up) Buy extra tokens when: * a reporting deadline requires extra searches * you are expanding market coverage temporarily * you need manual tracker runs before a meeting * an AI Visibility campaign needs more prompt or platform coverage * your subscription renewal is not soon enough ## Buy a top-up [Section titled “Buy a top-up”](#buy-a-top-up) 1. Open [Billing](https://app.isearchfrom.com/billing). 2. Confirm the correct organization is active. 3. Find **Token Top-Ups** under the plan selector. 4. Choose a token pack. 5. Review checkout details. 6. Complete payment through Stripe. The added tokens become available to the active organization after checkout completes. ## How top-up tokens are spent [Section titled “How top-up tokens are spent”](#how-top-up-tokens-are-spent) * Subscription tokens are used first. * Top-up tokens are used only after subscription tokens run out. * Top-up tokens remain available across subscription renewals. * A refunded top-up can remove the related permanent tokens. * Top-ups belong to the organization that bought them, not to a personal user account. ## Top-up tips [Section titled “Top-up tips”](#top-up-tips) * Review recent usage on [Billing](https://app.isearchfrom.com/billing) before buying. * Pause unused campaigns before increasing spend. * Use larger packs only when you have a clear usage plan. * Keep billing owners informed if multiple teammates can run campaigns. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app. Token Top-Ups are on the Billing page under the plan selector. Top-ups are added to the active organization. ## Related docs [Section titled “Related docs”](#related-docs) * [Token Usage](/billing/token-usage/) * [Manage Subscription](/billing/manage-subscription/) * [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) # Token Usage > Understand where tokens are used and how to review organization usage. Tokens are organization usage credits. They are used by features that perform captured searches, recurring tracker checks, or AI Visibility checks. ## Where to review usage [Section titled “Where to review usage”](#where-to-review-usage) Billing Open [Billing](https://app.isearchfrom.com/billing) to see token balance cards, the Token Usage chart, the current plan, next billing date, top-ups, subscription history, and payment history. Sidebar token meter The app sidebar shows a quick token balance for the active organization while you work. Team Settings Open [Team Settings](https://app.isearchfrom.com/settings/team) to review the Token Transactions ledger. It can be filtered by member, wallet type, and resource. Active organization Token usage is organization-level. If you belong to multiple organizations, switch to the correct organization before reviewing balances or transactions. Missing usage data If token data is unavailable, confirm that a organization is active and that your role has access. ## How token balances work [Section titled “How token balances work”](#how-token-balances-work) * Subscription tokens Subscription tokens are the included tokens from your current plan. Internally, these are the resettable wallet. They refresh to the plan allocation on each billing renewal. Monthly plans refresh monthly. Yearly plans refresh yearly and receive the annual allocation for the yearly period. These tokens are spent first. Unused subscription tokens do not stack on top of the next renewal allocation. * Permanent tokens Permanent tokens are extra tokens bought through Token Top-Ups. Internally, these are the permanent wallet. They do not reset when the subscription renews. They stay available for the organization and are used after subscription tokens run out. Refunds or billing reversals can remove the related top-up tokens. * Total available The product combines both wallets when checking whether a organization has enough tokens. A organization can still run token-using work when subscription tokens are empty if permanent top-up tokens remain. ## What spends tokens [Section titled “What spends tokens”](#what-spends-tokens) * Searches Direct Search does not spend tokens because it builds a search-engine URL and sends you out to the search engine. Search Engine Proxy spends tokens because iSearchFrom captures and stores result pages. Each requested proxy results page adds work, so start with page 1 and add deeper pages only when needed. * Trackers SERP Position Tracker and Ads Position Tracker spend tokens when a scheduled or manual run is created. Usage scales with the number of keywords checked in that run and the run frequency. Tracked domains affect what the campaign reports, but token usage follows the checks the run performs. * AI Visibility AI Visibility runs can spend tokens based on prompts, locations, platforms, and run frequency. ## When a check is charged [Section titled “When a check is charged”](#when-a-check-is-charged) A check is charged for the work of running the search, not for whether the answer was the one you hoped for. | Check result | Charged | Why | | ------------- | ------- | -------------------------------------------------------------------- | | **Completed** | Yes | The search ran and returned results. | | **No answer** | Yes | The search ran correctly; the platform simply had nothing to show. | | **Failed** | No | The check could not be completed, so the tokens are returned to you. | “No answer” is a real result A **no answer** check is not an error, and it is not a wasted check. It means the platform was queried successfully and genuinely returned no content for that prompt — most often because Google did not render an AI Overview for that query. This is common and expected. Google shows an AI Overview for only a minority of queries, and searches looking for a nearby business very often return a local map pack instead. Knowing that no AI Overview appears for a query is itself useful: it tells you the answer box is not a channel you are losing, because it is not there to win. If you want fewer of these, track prompts that ask a question rather than name a place — informational and comparison prompts trigger AI answers far more often than local ones. Failed checks are refunded automatically. You do not need to request anything, and the refund appears in the token ledger below. ## Estimate usage before creating campaigns [Section titled “Estimate usage before creating campaigns”](#estimate-usage-before-creating-campaigns) For trackers, estimate: ```text keywords x run frequency ``` For AI Visibility, usage is affected by: ```text prompts x locations x platforms x run frequency ``` Start with narrow campaigns, inspect results, then expand. ## Reading the token ledger [Section titled “Reading the token ledger”](#reading-the-token-ledger) Team Settings includes a Token Transactions table for the active organization. Use it when you need to explain why balance changed. The table shows: * amount: positive numbers add tokens, negative numbers spend or reverse tokens * balance after: wallet balance after that transaction * wallet: subscription/resettable or permanent/top-up * resource: the tool or billing event that caused the transaction * time: when the transaction was created Filter by member to see who triggered usage. Filter by resource to isolate proxy searches, tracker runs, AI Visibility runs, subscription resets, orders, or refunds. ## Reduce unnecessary usage [Section titled “Reduce unnecessary usage”](#reduce-unnecessary-usage) Pause stale campaigns Pause campaigns that no longer support decisions or reports. Start weekly Use weekly schedules before daily schedules. Keep AI prompts focused Remove generic prompts that do not reveal useful brand or competitor signal. Split broad monitoring Use smaller campaigns by market, intent, or client instead of one oversized setup. Load pages intentionally Load extra proxy pages only when the first page does not answer the question. Remove setup tests Remove test campaigns after setup is complete. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app. Token usage is shown on the Billing page in the Token Usage chart and the balance cards. Open [Team Settings](https://app.isearchfrom.com/settings/team) for the detailed token transaction ledger. ## Related docs [Section titled “Related docs”](#related-docs) [Plans, Tokens, and Limits](/guides/plans-tokens-limits/)Understand plan access, limits, and recommended usage patterns. [Token Top-ups](/billing/token-top-ups/)Add extra usage capacity outside your subscription allocation. [Schedules and Alerts](/tracking/schedules-alerts/)Tune recurring campaign frequency and notifications. # Cookbook: Send alerts to Slack > Set a webhook URL on a tracking campaign so iSearchFrom posts alerts to a small relay, which reformats them and forwards to a Slack channel. **Goal:** when a tracking campaign raises an alert (a big ranking drop, a new competitor ad, a visibility change), have it appear in a Slack channel automatically. Tip Slack is now a built-in alert channel: connect your workspace under **Settings → Alerts & channels** and pick a channel, no relay needed. See [Alert Channels](/tracking/alert-channels/). This recipe still fits plans without alert channels, or when you want to reshape the message yourself. ## You’ll need [Section titled “You’ll need”](#youll-need) * A campaign with **alerts enabled** — SERP Position Tracker, Ads Position Tracker, AI Visibility, Maps Visibility or Marketplace. * A place to run a tiny **relay** — a serverless function (Cloudflare Workers, Vercel, AWS Lambda) or any small always-on endpoint. * A **Slack incoming webhook** URL for the target channel. ## Why a relay? [Section titled “Why a relay?”](#why-a-relay) iSearchFrom delivers alerts as its **own** JSON payload (below). Slack’s incoming webhooks expect Slack’s format. A relay is a \~15-line function that receives iSearchFrom’s payload and reposts it to Slack as a readable message. It also keeps your Slack webhook URL private. ## The payload iSearchFrom sends [Section titled “The payload iSearchFrom sends”](#the-payload-isearchfrom-sends) When an alert fires, iSearchFrom `POST`s this to your campaign’s webhook URL: ```json { "event": "tool.alert.created", "timestamp": "2026-07-07T14:05:00.000Z", "data": { "alertId": "…", "targetType": "serp_position_campaigns", "campaignId": "…", "campaignName": "Brand keywords — US", "type": "…", "severity": "…", "metadata": { "…": "…" } } } ``` Delivery behavior iSearchFrom makes **4 attempts** in all, waiting about 5s, 10s and 20s between them, so a relay that is down for more than about half a minute misses the alert. A `4xx` other than `408` or `429`, or a redirect, is not retried. Make your relay **idempotent** on `data.alertId` if duplicate posts would bother you, and return a `2xx` quickly. ## Steps [Section titled “Steps”](#steps) ### Create the relay [Section titled “Create the relay”](#create-the-relay) This Cloudflare Worker receives iSearchFrom’s payload and forwards a formatted message to Slack. The Slack webhook URL is stored as a secret, never exposed to iSearchFrom. ```js export default { async fetch(request, env) { if (request.method !== 'POST') return new Response('ok'); const { event, data } = await request.json(); if (event !== 'tool.alert.created') return new Response('ignored'); const text = `:rotating_light: *${data.severity ?? 'alert'}* on *${data.campaignName}*\n` + `Type: ${data.type} · Campaign: \`${data.campaignId}\``; await fetch(env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) }); return new Response('ok'); // 2xx tells iSearchFrom the delivery succeeded } }; ``` Deploy it and set the Slack webhook as a secret: ```bash wrangler secret put SLACK_WEBHOOK_URL # paste your Slack incoming-webhook URL wrangler deploy ``` You’ll get a public relay URL like `https://serp-alerts.your-workers.dev`. ### Point the campaign at the relay [Section titled “Point the campaign at the relay”](#point-the-campaign-at-the-relay) Enable alerts on the campaign and set its **alert webhook URL** to your relay URL — in the app’s campaign **alert settings**, or via the campaign’s `alertSettings` when you create/update it through the API. See [Schedules & alerts](/tracking/schedules-alerts/). ### Trigger a test [Section titled “Trigger a test”](#trigger-a-test) Cause an alert (or wait for the next run to raise one). iSearchFrom posts to your relay, which forwards to Slack. ## Verify [Section titled “Verify”](#verify) * A message appears in your Slack channel when an alert fires. * Your relay logs show a `POST` with `event: "tool.alert.created"` and returns `2xx`. * If nothing arrives: confirm the campaign has at least four completed runs and the alert type is switched on, the webhook URL is correct and reachable, and your relay returns `2xx` (`5xx`, `408`, `429` or a timeout trigger retries, then the alert is given up; any other `4xx` gives up at once). ## Variations [Section titled “Variations”](#variations) * **Rich Slack formatting** — swap the `{ text }` body for Slack Block Kit to include the metadata fields and a link back to the campaign. * **Other destinations** — the same relay pattern forwards to Microsoft Teams, Discord, PagerDuty, or an internal endpoint; just change the outbound `fetch`. * **Filter noise** — have the relay drop low-severity alerts or de-duplicate on `alertId`. ## Related [Section titled “Related”](#related) [Schedules & alerts](/tracking/schedules-alerts/)Enabling alerts and setting thresholds. [Webhooks](/reference/webhooks/)The alert webhook contract in the reference. [AI Visibility alerts](/ai-visibility/alerts/)Alerts for AI-visibility campaigns. # Cookbook: Automate SERP monitoring with the API > A complete Node.js script that runs localized proxy searches for a set of queries, waits for results, and writes them to JSON — ready to run on a schedule. **Goal:** run a set of localized Google searches on a cadence (say, nightly), capture the parsed results, and write them somewhere you can diff or feed into a report — with no manual clicking. ## You’ll need [Section titled “You’ll need”](#youll-need) * An organization with the **Search Engine Proxy** feature and token balance. * An [API key](/reference/authentication-api-keys/) with the **Search** permission. * Node.js 18+ (for the built-in `fetch`), or adapt the logic to any language. ## How proxy search works [Section titled “How proxy search works”](#how-proxy-search-works) A proxy search is **asynchronous**: you `POST` the search, get back an id with `status: "pending"`, then poll `GET …/{id}` until it’s `completed`. Each requested page costs one token. The script below wraps that create-then-poll loop. ## The script [Section titled “The script”](#the-script) Save as `monitor.mjs`: ```js const BASE_URL = process.env.ISEARCHFROM_BASE_URL ?? 'https://api.isearchfrom.com/v1'; const API_KEY = process.env.ISEARCHFROM_API_KEY; if (!API_KEY) throw new Error('Set ISEARCHFROM_API_KEY'); // The searches to run each cycle. Add or edit freely. const QUERIES = [ { query: 'best crm software', country: 'US', language: 'en' }, { query: 'crm für startups', country: 'DE', language: 'de' }, { query: 'melhor crm', country: 'BR', language: 'pt' } ]; async function api(path, init = {}) { const res = await fetch(`${BASE_URL}${path}`, { ...init, headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, ...(init.headers ?? {}) } }); const body = await res.json().catch(() => ({})); if (!res.ok) { // body has { statusCode, code, message } — branch on `code`, log `message`. throw new Error(`API ${res.status} (code ${body.code}): ${body.message}`); } return body; } async function runSearch({ query, country, language }) { // 1. Create the search (1 page = 1 token). const created = await api('/tools/serp/proxy-search', { method: 'POST', body: JSON.stringify({ query, country, language, engine: 'google', device: 'desktop', pages: [1] }) }); // 2. Poll until completed / failed / timeout. const deadline = Date.now() + 90_000; let search = created; while (search.status === 'pending' || search.status === 'running') { if (Date.now() > deadline) throw new Error(`Timed out: ${query}`); await new Promise((r) => setTimeout(r, 2000)); search = await api(`/tools/serp/proxy-search/${created.id}`); } return search; } const results = []; for (const q of QUERIES) { try { const search = await runSearch(q); results.push({ ...q, status: search.status, search }); console.error(`✓ ${q.query} (${q.country}) → ${search.status}`); } catch (err) { results.push({ ...q, status: 'error', error: String(err) }); console.error(`✗ ${q.query} (${q.country}) → ${err.message}`); } } // 3. Emit JSON on stdout so you can redirect it to a file or a pipeline. process.stdout.write(JSON.stringify({ ranAt: new Date().toISOString(), results }, null, 2)); ``` Run it and write today’s snapshot to a file: ```bash export ISEARCHFROM_API_KEY="isk_your_key_here" node monitor.mjs > "serp-$(date +%F).json" ``` Check balance before big batches Before a large run, `GET /v1/tokens/balance` so you fail fast instead of mid-batch on `39012` (insufficient balance). Each page of each query costs one token. ## Run it on a schedule [Section titled “Run it on a schedule”](#run-it-on-a-schedule) **cron (Linux/macOS)** — nightly at 02:00: ```text 0 2 * * * cd /path/to/monitor && ISEARCHFROM_API_KEY=isk_... /usr/bin/node monitor.mjs > "serp-$(date +\%F).json" 2>> monitor.log ``` **GitHub Actions** — a scheduled workflow (store the key as a repository secret): ```yaml name: serp-monitor on: schedule: - cron: '0 2 * * *' jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: node monitor.mjs > snapshot.json env: ISEARCHFROM_API_KEY: ${{ secrets.ISEARCHFROM_API_KEY }} - uses: actions/upload-artifact@v4 with: { name: serp-snapshot, path: snapshot.json } ``` ## Verify [Section titled “Verify”](#verify) * Each completed entry has `search.status === "completed"` and pages with parsed organic results and ads. * Failed queries carry an `error` with the API `code` and `message` — handle those (bad country, insufficient balance, rate limit) rather than blindly retrying. See [Errors, pagination & rate limits](/reference/errors-pagination-rate-limits/). ## Variations [Section titled “Variations”](#variations) * **Diff over time** — commit each snapshot and diff the JSON to spot ranking or ad changes. * **More pages** — set `pages: [1, 2, 3]` to capture deeper results (costs one token per page). * **Let an agent do it** — the same flow is available to an [MCP agent](/mcp/overview/) via `run_proxy_search` + `get_proxy_search`, with no script to maintain. * **Respect the rate limit** — the default is 600 requests/minute; for large query sets add a small delay between searches. ## Related [Section titled “Related”](#related) [Search Engine Proxy](/search-tools/search-engine-proxy/)The proxy search tool in depth. [Interactive API reference](https://api.isearchfrom.com/docs/api-key)Exact request/response schemas. [Search Parameters](/reference/search-parameters/)Country, language, device, engine, SafeSearch. # Cookbook: Track rankings across countries > Monitor the same keywords from several countries on a schedule with the SERP position tracker, and read the movements over time. **Goal:** track how a set of keywords ranks for your domain in, say, the US, UK, and Germany — refreshed automatically every week — and see which keywords moved. ## You’ll need [Section titled “You’ll need”](#youll-need) * An organization with the **SERP Position Tracker** feature and available token balance. * The keywords you want to track and the domain you’re tracking for. * To automate it: an [API key](/reference/authentication-api-keys/) with the **Search** permission, or an [MCP client](/mcp/overview/). ## The approach [Section titled “The approach”](#the-approach) One campaign tracks one country’s SERP. To cover three countries you create **one campaign per country** with the same keywords, each on a weekly schedule. Then you read each campaign’s latest run to compare positions. ## Steps [Section titled “Steps”](#steps) * In the app ### Create the first campaign [Section titled “Create the first campaign”](#create-the-first-campaign) In [app.isearchfrom.com](https://app.isearchfrom.com), open **Position Tracker → New campaign**. Name it clearly, e.g. `Brand keywords — US`. Set the domain, add your keywords, and choose **country US**, your language, device, and engine. ### Add a schedule [Section titled “Add a schedule”](#add-a-schedule) Turn on a schedule and pick **weekly** at a time that suits you. See [Schedules & alerts](/tracking/schedules-alerts/) for cron and timezone details. ### Duplicate for each country [Section titled “Duplicate for each country”](#duplicate-for-each-country) Create two more campaigns — `… — UK` (country GB) and `… — DE` (country DE) — with the **same keywords** but the matching country and language. ### Read the results [Section titled “Read the results”](#read-the-results) Each run produces per-keyword positions. Open a campaign to see current ranks, and compare across your three campaigns. See [Reading tracker results](/tracking/reading-tracker-results/). * With the API Create one campaign per country. The body is the same except `country` and `language`: ```bash BASE_URL="https://api.isearchfrom.com/v1" for pair in "US:en" "GB:en" "DE:de"; do country="${pair%%:*}"; lang="${pair##*:}" curl -s -X POST "$BASE_URL/tools/serp/position-tracker/campaigns" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"Brand keywords — $country\", \"domain\": \"example.com\", \"keywords\": [\"best crm software\", \"crm for startups\"], \"country\": \"$country\", \"language\": \"$lang\", \"device\": \"desktop\", \"engine\": \"google\", \"schedule\": { \"cron\": \"0 8 * * 1\", \"timezone\": \"UTC\" } }" done ``` Confirm the exact field names Campaign fields and their constraints are defined by the API’s schema. Check the **[interactive API reference](https://api.isearchfrom.com/docs/api-key)** for the exact request body — it’s generated from the live API, so it’s always current. Trigger an immediate first run (optional — schedules run on their own): ```bash curl -X POST "$BASE_URL/tools/serp/position-tracker/campaigns/$CAMPAIGN_ID/runs" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" ``` Read the latest run and its per-keyword positions: ```bash curl "$BASE_URL/tools/serp/position-tracker/campaigns/$CAMPAIGN_ID/runs?limit=1" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" ``` * With an AI agent (MCP) Once you’ve [connected a client](/mcp/connect/), ask in natural language: ```text Using iSearchFrom, create three position-tracking campaigns for example.com with the keywords "best crm software" and "crm for startups" — one for the US (en), one for the UK (en), and one for Germany (de) — each on a weekly Monday 08:00 schedule. Then trigger a first run for each and tell me the starting positions. ``` The agent calls `create_position_campaign` three times, `trigger_position_run` for each, and reads the results with `get_position_run`. See the [tool catalog](/mcp/tools/). ## Verify [Section titled “Verify”](#verify) * Each campaign shows a completed run with a position for every keyword. * Re-running next week, the campaign shows movement (up/down) per keyword. * Token balance dropped by roughly `keywords × countries` per run — that’s expected; each keyword check costs tokens. See [Token usage](/billing/token-usage/). ## Variations [Section titled “Variations”](#variations) * **Get notified on big moves** — add alert rules and a webhook, then relay them to Slack. See [Send alerts to Slack](/cookbooks/alert-webhooks-to-slack/). * **Cities, not just countries** — set a `city` for hyper-local SERPs (great for local SEO). See [Local SEO](/use-cases/local-seo/). * **Track competitors too** — add competitor domains to the campaign to see where they rank for the same terms. ## Related [Section titled “Related”](#related) [SERP Position Tracker](/tracking/serp-position-tracker/)The tracker in depth. [Schedules & alerts](/tracking/schedules-alerts/)Cron, timezones, and alert rules. [Reading tracker results](/tracking/reading-tracker-results/)Interpreting positions and movement. # Cookbooks > Task-focused recipes that combine iSearchFrom features end to end — each one a complete, copy-paste-ready walkthrough for a real job. Guides explain **one** feature. Cookbooks solve a **whole task** — they stitch several features together into a complete, working recipe you can follow start to finish. Every recipe states its goal, what you need, the exact steps, runnable code where relevant, and how to verify it worked. Each recipe is self-contained. Pick the one that matches what you’re trying to do: [Track rankings across countries](/cookbooks/multi-country-rank-tracking/)Monitor the same keywords from several countries on a schedule, and read the movements. [Automate SERP monitoring with the API](/cookbooks/automate-serp-monitoring/)A complete script that runs localized searches and exports the results on a cadence. [Send alerts to Slack](/cookbooks/alert-webhooks-to-slack/)Wire campaign alerts to a Slack channel with an incoming-webhook relay. ## How recipes are structured [Section titled “How recipes are structured”](#how-recipes-are-structured) Every cookbook follows the same shape so you can scan or follow along: 1. **Goal** — what you’ll have when you’re done. 2. **You’ll need** — accounts, features, permissions, and tools. 3. **Steps** — numbered, with UI paths or runnable code. 4. **Verify** — how to confirm it works. 5. **Variations** — common tweaks and where to go deeper. ## Two ways to do most things [Section titled “Two ways to do most things”](#two-ways-to-do-most-things) Almost every recipe can be done in the **app** (point-and-click) or via the **API / MCP** (automation). Recipes show whichever fits the task, and link to the reference when you want the full surface: In the app Best for one-off and interactive work. No code. Everything in the recipes maps to a screen in [app.isearchfrom.com](https://app.isearchfrom.com). With the API or MCP Best for scheduled jobs, pipelines, and AI agents. See the [API Overview](/reference/api/) and the [MCP Server](/mcp/overview/). # Asking Good Questions > What to ask Data Chat, how to narrow a question, and how follow-ups and sharing work. Ask the question you actually want answered. You do not need to know the table names, and you do not need to write SQL. ## Questions that work well [Section titled “Questions that work well”](#questions-that-work-well) Comparisons over time “Which keywords dropped out of the top 10 since last month?" Rankings and movement "What is my average position by country, worst first?” Competitors “Which advertisers appeared most often against my brand keywords?" AI visibility "Which prompts mention my competitors but not me?" Local coverage "Where on the grid am I outside the top 3?" Housekeeping "Which campaigns have not run in two weeks?” ## Narrow it, and it gets better [Section titled “Narrow it, and it gets better”](#narrow-it-and-it-gets-better) A vague question makes the model guess, and a guess is where wrong answers come from. Three things make the biggest difference: 1. **Name the window.** “Since 1 August” beats “recently”. If you do not say, it will choose one — and tell you which in the SQL. 2. **Name the campaign or project** if you have several. Otherwise the answer spans all of them, which is sometimes what you want and sometimes not. 3. **Say what “best” means.** Best by average position, by number of keywords in the top 3, or by improvement? Each is a different query. It answers about tracked data only “How do I rank for cordless vacuums?” cannot be answered unless a campaign has been tracking that keyword. Data Chat reads what your campaigns already collected — it does not run a new search. To check something untracked, use [Direct Search](/search-tools/direct-search/) or add it to a tracker. ## Follow-ups [Section titled “Follow-ups”](#follow-ups) A conversation keeps its context, so the second question can be short: > **You:** Which keywords lost top-3 positions last month? **You:** Just the ones in Germany. **You:** Now show those as a chart. Suggested follow-ups appear under each answer — the questions the data itself raises. They are a good way to find the question you should have asked. Long threads are compacted automatically as they grow, so a conversation stays affordable rather than re-sending its whole history every turn. ## Sharing a conversation [Section titled “Sharing a conversation”](#sharing-a-conversation) Each conversation has one of two visibilities: | Visibility | Who can read it | | ---------------- | ----------------------------- | | **Organization** | anyone in your workspace | | **Restricted** | you, plus the people you name | Sharing is inside your workspace only — a conversation has no public link, and switching a conversation to restricted does not hide it from workspace admins. ## Drafts [Section titled “Drafts”](#drafts) An unsent question is kept as you type, so navigating away and coming back does not cost you the sentence you were composing. # Cost and Limits > Data Chat bills what the question actually cost to answer, and caps how far one question can go. Data Chat does not charge a flat price per question. It measures what the answer actually cost to produce and converts that into wallet tokens, so a cheap question is cheap. ## Why billing works this way [Section titled “Why billing works this way”](#why-billing-works-this-way) A flat price per question would be simpler to explain and worse for you. Answers are not the same size. A one-table lookup and a six-query analysis across a year of runs differ by more than fifty times in real cost. Charging both the same means light users subsidise heavy ones. It also matters for **follow-ups**. A second question in a warm thread costs a fraction of the first, because the context is already loaded. Cost-passthrough passes that saving to you: the follow-up bills as a fraction. Under a flat price it would cost the same as the first question, and the saving would quietly become margin. Follow-ups are the cheap way to work Refining in one thread costs far less than opening a new conversation and restating everything. Ask broadly, then narrow. ## Roughly what a question costs [Section titled “Roughly what a question costs”](#roughly-what-a-question-costs) | Question | Typical cost | | ----------------------------------------------------- | -------------------------- | | A simple lookup — “how many campaigns do I have?” | a few tokens | | A normal analysis — “which keywords fell last month?” | around twenty tokens | | A heavy cohort analysis across many runs | a couple of hundred tokens | Exact figures depend on how much data the question has to read. The tokens spent appear against the conversation, so a thread’s cost is never a surprise at the end of the month. ## Limits on a single question [Section titled “Limits on a single question”](#limits-on-a-single-question) Every question is bounded, so one query cannot run away with your wallet or the database: | Limit | What it does | | ----------------------------- | ------------------------------------------------------------------------------------------------------ | | **Queries per question** | How many SQL steps one answer may take — enough to explore the schema and answer, not enough to wander | | **Result bytes per query** | Caps how much data a single query returns; a huge result is truncated rather than loaded whole | | **Answer length** | Caps the written answer, so a reply stays readable | | **Monthly workspace ceiling** | A backstop on total spend per organization per month, checked *before* an answer runs | The monthly ceiling stops a runaway rather than billing you for one — if it is reached, Data Chat stops answering until the next month rather than presenting an invoice you did not expect. ## Plans [Section titled “Plans”](#plans) Data Chat is a plan feature. If your workspace is not entitled, the tool shows what it does rather than answering, and the upgrade path is in [Manage subscription](/billing/manage-subscription/). Tokens come from the same wallet as every other tool, so a heavy analysis month and a heavy tracking month draw on the same balance. See [Token usage](/billing/token-usage/) for where they went. # AI Data Chat Overview > Ask your own tracking data a question in plain language, and get the SQL and rows behind the answer. Data Chat answers questions about the data your campaigns have already collected — rankings, ads, AI mentions, Maps grids, saved searches — without you building a report or exporting anything. Ask *“which keywords did I lose top-3 positions for last month?”* and you get a sentence, the rows it was derived from, the SQL that produced them, and a chart where one helps. ## Evidence, not just an answer [Section titled “Evidence, not just an answer”](#evidence-not-just-an-answer) The answer is the least trustworthy part of anything an AI tells you, so it is never the only thing you get. Every answer carries: The SQL it ran Every query, in order. If the answer looks wrong, the query tells you why. The rows it read The actual result set, not a summary of one. Named tables, real values. A chart, when it helps Drawn from those same rows — never from numbers invented in the prose. Follow-up prompts The next questions the data suggests, so a thread goes somewhere. Check the query, not the wording If a number surprises you, read the SQL before you believe or dismiss it. The most common cause of a surprising answer is a question that was ambiguous — “last month” and “the last 30 days” are different, and the query says which one it used. ## What it can see [Section titled “What it can see”](#what-it-can-see) Your organization’s own data across every tool, and nothing else: | Area | What it covers | | -------------------------- | ---------------------------------------------------------- | | **SERP Position Tracker** | campaigns, runs, checks, ranked results | | **Ads Position Tracker** | campaigns, runs, checks, ad results and creatives | | **Maps Visibility** | campaigns, runs, checks, grid results, places | | **Marketplace Visibility** | campaigns, runs, checks, listing results, products | | **AI Visibility** | campaigns, runs, checks, mentions, ad creatives | | **Searches** | proxy searches with their pages and items, direct searches | | **Workspace** | projects, alerts, schedules | ## What it cannot do [Section titled “What it cannot do”](#what-it-cannot-do) Being clear about this is part of trusting it: * **It reads. It never writes.** No campaign is created, changed, paused or deleted by asking. The connection it uses is read-only at the database level, not merely instructed to behave. * **It cannot leave your organization.** Row-level security scopes every query to your own data, so a question can never reach another workspace — whatever the question says. * **It cannot see everything in a row.** A few columns are withheld from it entirely, alert webhook settings among them. * **It only knows what your campaigns collected.** It cannot look up a ranking you never tracked. If nothing ran, there is nothing to ask about. ## Or bring your own agent [Section titled “Or bring your own agent”](#or-bring-your-own-agent) Data Chat and the [MCP server](/mcp/overview/) are the same capability with a different model driving it. Both read the same catalogue of tables, through the same read-only database role, under the same row-level security. | | Data Chat | MCP server | | ------------------- | ---------------------------- | ----------------------------------------- | | Where you ask | in the app | Claude, Cursor, or any MCP client | | Whose model answers | ours | yours | | Reads | the same 25 tables | the same 25 tables | | Isolation | read-only role + RLS | read-only role + RLS | | Billing | per answer, by measured cost | your own model’s cost; our reads are free | Use Data Chat when you want an answer without leaving the app, or without paying for your own model. Use MCP when you already work in an agent, want your own model, or want to combine this data with something else you have connected. They are not alternatives you have to choose between Both are available at once. The same conversation you have in the app can be continued from an agent, because the data underneath is identical. ## Where to go next [Section titled “Where to go next”](#where-to-go-next) [Asking good questions](/data-chat/asking-questions/)What to ask, how to narrow it, and how follow-ups work. [Reading an answer](/data-chat/reading-answers/)The SQL, the rows, the chart — and how to tell a good answer from a confident one. [What it costs](/data-chat/cost-and-limits/)Data Chat bills by what the question actually cost, not a flat price. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Data Chat](https://app.isearchfrom.com/tools/ai/chat) in the app. # Reading an Answer > The SQL, the rows and the chart — and how to tell a good answer from a confident one. Every answer ships with its own evidence. Reading that evidence takes about ten seconds and is the difference between a number you can present and a number you are hoping is right. ## What arrives with an answer [Section titled “What arrives with an answer”](#what-arrives-with-an-answer) **The prose.** One or two sentences answering the question you asked. **The queries.** Every SQL statement it ran, in order, expandable. A question often takes more than one: look at the schema, sample a table, then answer. **The rows.** The actual result set of the final query, as a table, with real column names. Not a summary — the rows themselves. **A chart**, where the shape of the data supports one. Drawn from those rows. **Follow-up prompts** — the next questions this answer raises. ## How to sanity-check in ten seconds [Section titled “How to sanity-check in ten seconds”](#how-to-sanity-check-in-ten-seconds) 1. **Read the WHERE clause.** This catches the most common problem by far: a time window or a campaign filter that is not the one you meant. 2. **Check the row count against the prose.** “Three keywords fell” should sit above three rows. A mismatch is the clearest possible signal. 3. **Look at what it grouped by.** An average across campaigns and an average within one campaign are different numbers with the same name. Confidence is not accuracy A model will describe a wrong result as fluently as a right one. The prose cannot tell you which you have; the query can. When a figure is going into a client report, read the SQL. ## When an answer looks wrong [Section titled “When an answer looks wrong”](#when-an-answer-looks-wrong) Usually the question was ambiguous rather than the data being bad. In order of likelihood: | What you see | Usual cause | | ---------------------------- | ------------------------------------------------------- | | Numbers lower than expected | The window excluded runs you had in mind | | Numbers higher than expected | Several campaigns were included, not one | | “No results” | Nothing was tracked for that keyword, or in that window | | A column you did not expect | Your phrasing matched a different table | Ask the follow-up rather than rewriting from scratch — *“only campaign X”* or *“since 1 August instead”*. The context is kept, so a correction is one line. ## Empty answers [Section titled “Empty answers”](#empty-answers) If the query genuinely returns nothing, you get told that rather than given an invented summary. An empty result is a real answer: it usually means the data was never collected, not that the number is zero. ## The same data, through an agent [Section titled “The same data, through an agent”](#the-same-data-through-an-agent) Data Chat reads exactly the same catalogue the [MCP server](/mcp/overview/) exposes to external agents, under the same read-only role and the same row-level security. If you would rather ask from Claude or Cursor than from the app, `query_sql` reaches the same tables with the same guarantees. [What it costs](/data-chat/cost-and-limits/)Billing follows the real cost of the question. # Cloud storage buckets > Export Parquet, CSV, JSON Lines or SQL files to your own Amazon S3, Google Cloud Storage, Cloudflare R2, Wasabi, Backblaze B2, DigitalOcean Spaces or MinIO bucket — key layout, manifests, and the minimal permissions needed. A cloud storage destination writes files into **your own bucket**, laid out for a data lake: one file per dataset per run, in date folders, with a manifest written last. It works with Amazon S3 and any S3-compatible store — Google Cloud Storage, Cloudflare R2, Wasabi, Backblaze B2, DigitalOcean Spaces, MinIO and others. From the bucket, [warehouse recipes](/exports/warehouse-recipes/) load the files into Snowflake, Redshift, Databricks or DuckDB. Bucket destinations are part of the [Agency plan](/exports/overview/#plans-and-limits). ## Settings [Section titled “Settings”](#settings) | Setting | Notes | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | **Storage provider** | Pick yours — iSearchFrom fills in its endpoint and addressing. See [providers](#providers). | | **Bucket** | The bucket name. | | **Region** | The bucket’s region, e.g. `eu-central-1`. Not asked for Google Cloud Storage and Cloudflare R2. | | **Account ID** | Cloudflare R2 only — it forms your bucket’s endpoint. | | **Endpoint** | MinIO and other stores only: the store’s `https://` S3 endpoint. For every other provider the endpoint in use is shown below the form. | | **Folder** | Optional prefix inside the bucket, e.g. `isearchfrom`. Everything is written under it. | | **Path-style addressing** | MinIO and other stores only: address the bucket as `endpoint/bucket` instead of `bucket.endpoint`. | | **Access key ID** | The key iSearchFrom signs in with. It tells you which key is in use. | | **Secret access key** | Stored encrypted and never shown again. When editing, leave it empty to keep the saved one. | | **Format** | Parquet, CSV, JSON Lines or SQL. See [file formats](/exports/formats/). | **Test connection** writes a small marker object, `_isearchfrom_connection_test`, under your folder and then deletes it. If the delete is not allowed the test still passes and the marker stays; it is harmless. ## Providers [Section titled “Providers”](#providers) | Provider | You enter | Endpoint used | Region | Addressing | | ------------------------ | ------------------- | ----------------------------------------------- | --------------------------------- | -------------- | | **Amazon S3** | Region | Amazon’s own | The bucket’s, e.g. `eu-central-1` | Virtual-hosted | | **Google Cloud Storage** | — | `https://storage.googleapis.com` | `auto` | Path-style | | **Cloudflare R2** | Account ID | `https://.r2.cloudflarestorage.com` | `auto` | Path-style | | **Wasabi** | Region | `https://s3..wasabisys.com` | The bucket’s, e.g. `eu-central-1` | Path-style | | **Backblaze B2** | Region | `https://s3..backblazeb2.com` | The bucket’s, e.g. `us-west-004` | Path-style | | **DigitalOcean Spaces** | Region | `https://.digitaloceanspaces.com` | The datacenter, e.g. `nyc3` | Virtual-hosted | | **MinIO** | Endpoint and region | Yours | `us-east-1` unless you changed it | Path-style | | **Other S3-compatible** | Endpoint and region | Yours | What the store expects | Your choice | Every endpoint must be reachable from the internet over HTTPS — endpoints on private networks are refused. ## What gets written [Section titled “What gets written”](#what-gets-written) ```text //dt=/. /_manifests/.json ``` For example, with the folder `isearchfrom`: ```text isearchfrom/position_tracker-results/dt=2026-09-30/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz isearchfrom/alerts/dt=2026-09-30/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz isearchfrom/_manifests/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.json ``` * **``** is the dataset id with `.` replaced by `-`: `position_tracker.results` → `position_tracker-results`. * **`dt=`** is the UTC date the run’s window ends — Hive-style, so Athena, Spark, DuckDB and friends recognize it as a partition. * **One file per dataset per run.** A retried run writes the same key again, so a retry overwrites rather than duplicates. * **``** follows the format: `parquet`, `csv.gz`, `ndjson.gz` or `sql.gz`. CSV, JSON Lines and SQL files are gzip-compressed and also stored with `Content-Encoding: gzip`, so some HTTP clients decompress them transparently on download. Parquet compresses itself and is never gzipped. ### The manifest [Section titled “The manifest”](#the-manifest) The manifest is written **after** every file of the run is complete. If you see a manifest, every file it lists is whole — so load by manifest, not by listing the bucket. ```json { "run_id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11", "destination_id": "0199a2f0-1c4d-7e55-8a21-6b7f0c9d2e33", "window_from": "2026-09-30T05:00:00.000Z", "window_to": "2026-09-30T05:55:00.000Z", "format": "csv", "files": [ { "dataset": "position_tracker.results", "key": "isearchfrom/position_tracker-results/dt=2026-09-30/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz", "rows": 1240, "bytes": 48213, "schema_version": 1, "columns": [ { "name": "row_id", "type": "string", "nullable": false, "description": "Stable id of this row; unique within the dataset." } ] } ] } ``` `format` is `parquet`, `csv`, `ndjson`, `sql_postgres` or `sql_mysql`. `columns` lists the columns in the file, in file order — every column of the dataset, or only the [ones you picked](/exports/overview/#choosing-columns) — with its type, whether it can be empty, and its description, the same as the [dataset reference](/exports/datasets/). `bytes` is the size of the file as stored. Files without a manifest A run that fails part-way may leave files that no manifest lists. Its data is delivered again by the next run, under a new `run_id`. Ignore unlisted files, and de-duplicate rows on `row_id`. ### Which format for a bucket [Section titled “Which format for a bucket”](#which-format-for-a-bucket) **Parquet** is the best choice for a data lake: typed columns, small files, and read directly by Snowflake, Redshift, Databricks, BigQuery, DuckDB and pandas. CSV, JSON Lines and SQL work too; Excel isn’t offered for buckets. See [file formats](/exports/formats/). ## Amazon S3 [Section titled “Amazon S3”](#amazon-s3) Create an IAM user (or role credentials) for iSearchFrom with write access to the folder only. Nothing needs to be read or listed: ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "IsearchfromExports", "Effect": "Allow", "Action": ["s3:PutObject", "s3:AbortMultipartUpload", "s3:DeleteObject"], "Resource": "arn:aws:s3:::your-bucket/isearchfrom/*" } ] } ``` * `s3:PutObject` writes the files and manifests (large files are uploaded in parts, which this permission also covers). * `s3:AbortMultipartUpload` cleans up an upload that was interrupted. * `s3:DeleteObject` is only used to remove the connection-test marker. Leave it out if you prefer; the test still passes. If the bucket uses SSE-KMS encryption, the credentials also need `kms:GenerateDataKey` and `kms:Decrypt` on the key (multipart uploads need both). Choose **Amazon S3** as the storage provider and set **Region** to the bucket’s region. ## Google Cloud Storage [Section titled “Google Cloud Storage”](#google-cloud-storage) Google Cloud Storage takes S3-compatible requests through its XML API, signed with an **HMAC key**: 1. Create a service account for iSearchFrom, and on the bucket grant it **Storage Object User** (`roles/storage.objectUser`). **Storage Object Creator** alone is not enough: a retried run replaces its own file, which needs permission to overwrite. 2. In the Cloud console, open **Cloud Storage → Settings → Interoperability** and create an HMAC key for that service account. 3. Choose **Google Cloud Storage** as the storage provider, and enter the HMAC key’s access ID as **Access key ID** and its secret as **Secret access key**. The endpoint (`https://storage.googleapis.com`), region (`auto`) and path-style addressing are set for you. ## Cloudflare R2 [Section titled “Cloudflare R2”](#cloudflare-r2) 1. In the Cloudflare dashboard, create an **R2 API token** with **Object Read & Write**, limited to the bucket. 2. Choose **Cloudflare R2** and enter your **Account ID** from the R2 dashboard. The endpoint `https://.r2.cloudflarestorage.com` and region `auto` follow from it. 3. Use the token’s access key ID and secret access key. ## Wasabi [Section titled “Wasabi”](#wasabi) Create an access key in the Wasabi console — ideally for a sub-user whose policy only allows writing to the bucket — choose **Wasabi**, and set **Region** to the bucket’s region, e.g. `us-east-1` or `eu-central-1`. ## Backblaze B2 [Section titled “Backblaze B2”](#backblaze-b2) Create an **application key** with **Read and Write** access, restricted to the bucket. Choose **Backblaze B2**, and set **Region** from the bucket’s S3 endpoint: for `s3.us-west-004.backblazeb2.com` the region is `us-west-004`. The key’s `keyID` is the **Access key ID** and its `applicationKey` the **Secret access key**. ## DigitalOcean Spaces [Section titled “DigitalOcean Spaces”](#digitalocean-spaces) Create a **Spaces access key** with read and write access to the bucket, choose **DigitalOcean Spaces**, and set **Region** to the datacenter the Space lives in, e.g. `nyc3`, `ams3` or `fra1`. ## MinIO and other S3-compatible stores [Section titled “MinIO and other S3-compatible stores”](#minio-and-other-s3-compatible-stores) Set **Endpoint** to the store’s public `https://` URL and **Region** to the one it expects (MinIO uses `us-east-1` unless configured otherwise). **Path-style addressing** is on for MinIO; for other stores, turn it on unless the store supports bucket subdomains. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Code | Likely cause | | ------------------------- | ----------------------------------------------------------------------------------------------- | | `not_found` | The bucket does not exist, or the region or endpoint points somewhere else. | | `access_denied` | Wrong access key or secret, or the policy doesn’t allow writing to the folder. | | `destination_rejected` | The store refused the request — check the region, path-style setting and bucket policies. | | `destination_unreachable` | The endpoint could not be reached, or it is on a private network. Temporary errors are retried. | See [all error codes](/exports/overview/#error-codes). ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Dataset reference](/exports/datasets/) * [File formats](/exports/formats/) * [Warehouse recipes](/exports/warehouse-recipes/) # BigQuery > Load exported datasets straight into tables in your own Google BigQuery project — setup, table layout, schema changes and de-duplication. A BigQuery destination loads your data into tables in **your own** Google Cloud project, through a Google account you connect. iSearchFrom creates the dataset and tables if they don’t exist, keeps their schema current, and appends new rows on every run. ## What you need [Section titled “What you need”](#what-you-need) * A Google Cloud project with BigQuery enabled. * A Google account connected under **Settings → Integrations** with **BigQuery** access. Google’s consent screen asks to view and manage your BigQuery data; this is the narrowest Google permission that can create tables and load files. * That Google account needs these roles in the project (or equivalent permissions): * **BigQuery Job User** (`roles/bigquery.jobUser`) on the project — to run load jobs. * **BigQuery Data Editor** (`roles/bigquery.dataEditor`) on the dataset — to create and update tables. If the dataset doesn’t exist yet, grant it on the project (or grant **BigQuery User**) so the dataset can be created. ## Settings [Section titled “Settings”](#settings) | Setting | Notes | | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | **Project ID** | The Google Cloud project id, e.g. `acme-analytics`. | | **Dataset ID** | Letters, digits and underscores, e.g. `isearchfrom`. Created on the first run if it doesn’t exist. | | **Location** | Where a new dataset is created — `US` by default, or e.g. `EU`, `europe-west1`. For an existing dataset, use its location. | | **Table prefix** | Put in front of every table name. `isf_` by default; may be empty. | **Test connection** checks that the connected account can reach the dataset — or, if it doesn’t exist yet, the project it will be created in. ## Tables [Section titled “Tables”](#tables) Each dataset gets its own table, named `` with `.` replaced by `__`: | Dataset | Table (default prefix) | | -------------------------- | ------------------------------- | | `position_tracker.results` | `isf_position_tracker__results` | | `position_tracker.checks` | `isf_position_tracker__checks` | | `ad_tracker.ads` | `isf_ad_tracker__ads` | | `maps.results` | `isf_maps__results` | | `marketplace.results` | `isf_marketplace__results` | | `ai_visibility.responses` | `isf_ai_visibility__responses` | | `ai_visibility.mentions` | `isf_ai_visibility__mentions` | | `search_history` | `isf_search_history` | | `search_history.results` | `isf_search_history__results` | | `alerts` | `isf_alerts` | Tables are **partitioned by day on `measured_at`** — filter on it to keep queries cheap. Each column carries its description from the [dataset reference](/exports/datasets/). Column types map like this: | Dataset type | BigQuery type | | -------------- | ------------------------- | | `string` | `STRING` | | `integer` | `INT64` | | `float` | `FLOAT64` | | `boolean` | `BOOL` | | `timestamp` | `TIMESTAMP` | | `json` | `JSON` | | `string_array` | `STRING`, mode `REPEATED` | Every column is `NULLABLE` in BigQuery, even ones that are never empty in the export. A column added later can only be added as nullable, so every table follows the same rule. Tables always have every column of the dataset. If you [picked columns](/exports/overview/#choosing-columns), the ones you left out are `NULL` in the rows loaded. ### Schema changes [Section titled “Schema changes”](#schema-changes) When a dataset gains a column, the next run adds it to the table automatically. Columns are never renamed, removed or retyped, so views and queries you build on these tables keep working. Rows loaded before a column existed have `NULL` in it. You can add your own tables and views to the same dataset. Don’t change the exported tables’ columns yourself. ## How loading works [Section titled “How loading works”](#how-loading-works) Data is loaded with BigQuery **load jobs**, not streaming inserts. Batch loading is free in BigQuery — you pay Google only for storage and your own queries — and each load job is atomic: a chunk is either fully in the table or not at all. Every load job has a fixed id derived from the run, so a retried run finds the chunk it already loaded instead of loading it twice. ## De-duplicating [Section titled “De-duplicating”](#de-duplicating) A single run never loads a row twice. Rows **can** repeat across runs when: * you **backfill** a date range with **Run now**, re-sending rows the table already has, or * a run failed part-way, and the next run delivered the same window again. Repeated rows have the same `row_id`. Query through a de-duplicating view: ```sql CREATE OR REPLACE VIEW `acme-analytics.isearchfrom.position_results` AS SELECT * FROM `acme-analytics.isearchfrom.isf_position_tracker__results` WHERE TRUE QUALIFY ROW_NUMBER() OVER (PARTITION BY row_id ORDER BY measured_at DESC) = 1; ``` Or clean the table itself after a backfill. Pause the destination first so no load runs while the table is replaced, and resume it afterwards: ```sql CREATE OR REPLACE TABLE `acme-analytics.isearchfrom.isf_position_tracker__results` PARTITION BY DATE(measured_at) AS SELECT * FROM `acme-analytics.isearchfrom.isf_position_tracker__results` WHERE TRUE QUALIFY ROW_NUMBER() OVER (PARTITION BY row_id ORDER BY measured_at DESC) = 1; ``` ## Example query [Section titled “Example query”](#example-query) Average position per keyword over the last 30 days: ```sql SELECT keyword, domain, ROUND(AVG(position), 1) AS avg_position, COUNT(*) AS checks FROM `acme-analytics.isearchfrom.position_results` WHERE measured_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY) AND position IS NOT NULL GROUP BY keyword, domain ORDER BY keyword, avg_position; ``` ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Code | Likely cause | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `reconnect_required` | The Google connection expired or was revoked. Reconnect it under **Settings → Integrations**; the destination resumes by itself. | | `access_denied` | The connected Google account lacks a role listed above, or BigQuery is not enabled in the project. | | `not_found` | The project doesn’t exist or isn’t visible to the connected account. | | `destination_rejected` | BigQuery refused a load — often a location that doesn’t match the existing dataset, or a table whose columns were changed by hand. | See [all error codes](/exports/overview/#error-codes). ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Dataset reference](/exports/datasets/) # Dataset reference > Every exportable dataset and every column in it — name, type, whether it can be empty, and what it means. This page lists every dataset you can [export](/exports/overview/) and every column in it. The same catalog is available from the API at `GET /v1/organizations/{organizationId}/exports/catalog`, and cloud storage [manifests](/exports/amazon-s3/#the-manifest) carry it with every file. ## Stability [Section titled “Stability”](#stability) * **Columns are only ever added** — never renamed, removed or retyped. * Adding a column raises the dataset’s **schema version**. Every webhook batch and storage manifest says which version it carries. * In files, columns are always in the order listed here; new columns are added at the end. * **Nullable: no** means the column always has a value. Every other column can be empty. Every dataset starts with the same two columns: * **`row_id`** — unique within the dataset, and the same every time the row is exported. De-duplicate on it. * **`measured_at`** — when the value was measured, in UTC. Warehouses partition on it. ## Picking columns [Section titled “Picking columns”](#picking-columns) You don’t have to export every column. When you set up an export, **Choose columns** next to a dataset lets you keep only the ones you need — say `keyword`, `domain` and `position` from `position_tracker.results`. `row_id` and `measured_at` are always included, and picked columns keep the order listed here. With a pick, columns a dataset gains later are not added until you pick them. See [choosing columns](/exports/overview/#choosing-columns) for what a pick means at each destination. ## Column types [Section titled “Column types”](#column-types) | Type | CSV | JSON Lines and webhooks | BigQuery | | -------------- | ------------------------------------------------ | ----------------------- | ------------------------- | | `string` | Text | String | `STRING` | | `integer` | Number | Number | `INT64` | | `float` | Number | Number | `FLOAT64` | | `boolean` | `true` / `false` | `true` / `false` | `BOOL` | | `timestamp` | ISO-8601 in UTC, e.g. `2026-09-30T06:02:14.512Z` | ISO-8601 string in UTC | `TIMESTAMP` | | `json` | JSON text | Object | `JSON` | | `string_array` | Values joined with `\|` | Array of strings | `STRING`, mode `REPEATED` | An empty value is an empty CSV cell, `null` in JSON, and `NULL` in BigQuery. How each type is written in Excel, SQL and Parquet files is in [file formats](/exports/formats/). ## When rows are exported [Section titled “When rows are exported”](#when-rows-are-exported) Campaign datasets are exported **with their run**: a row joins an export once the campaign run that produced it has finished, together with the rest of that run. Search history and alerts are exported by the time they were created. See [incremental delivery](/exports/overview/#schedules-and-incremental-delivery). ## `position_tracker.results` [Section titled “position_tracker.results”](#position_trackerresults) **Position tracker — rankings** — Every ranking position found, per keyword and domain. One row per ranking result recorded for a keyword in a Position tracker run. For the outcome of every keyword check, including failed ones, use `position_tracker.checks`. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | --------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `country` | string | yes | Country code the search ran in. | | `language` | string | yes | Language code. | | `device` | string | yes | Device type. | | `search_engine` | string | yes | Search engine. | | `city` | string | yes | City, when the search was localised. | | `keyword` | string | no | Keyword searched. | | `domain` | string | no | Domain found. | | `position` | integer | yes | Organic position. | | `page` | integer | yes | Results page the position was on. | | `url` | string | yes | Ranking URL. | | `title` | string | yes | Result title. | | `description` | string | yes | Result snippet. | ## `position_tracker.checks` [Section titled “position_tracker.checks”](#position_trackerchecks) **Position tracker — checks** — One row per keyword check, including those that failed. One row per keyword check in a Position tracker run, whether it succeeded or failed. It carries your best organic position in that check, so a daily sheet shows the rank without a join; every tracked domain’s position is in `position_tracker.results` (join on `run_id` and `keyword`). Schema version 2 (`best_position` and `best_url` were added in version 2). Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | --------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `country` | string | yes | Country code the search ran in. | | `language` | string | yes | Language code. | | `device` | string | yes | Device type. | | `search_engine` | string | yes | Search engine. | | `city` | string | yes | City, when the search was localised. | | `keyword` | string | no | Keyword checked. | | `status` | string | no | Check outcome. | | `error_code` | string | yes | Why the check failed, when it did. | | `best_position` | integer | yes | Your best organic position in this check (the project’s own domain when the campaign tracks it, otherwise any tracked domain); empty when you did not rank or the check failed. | | `best_url` | string | yes | Your URL that ranked at `best_position`; empty when `best_position` is empty. | ## `ad_tracker.ads` [Section titled “ad_tracker.ads”](#ad_trackerads) **Ad tracker — ads** — Every paid ad seen, per keyword. One row per paid ad seen for a keyword in an Ad tracker run. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | --------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `country` | string | yes | Country code the search ran in. | | `language` | string | yes | Language code. | | `device` | string | yes | Device type. | | `search_engine` | string | yes | Search engine. | | `city` | string | yes | City, when the search was localised. | | `keyword` | string | no | Keyword searched. | | `domain` | string | no | Advertiser domain. | | `position` | integer | yes | Ad position. | | `headline` | string | yes | Ad headline. | | `description` | string | yes | Ad text. | | `display_url` | string | yes | URL shown in the ad. | | `landing_url` | string | yes | URL the ad links to. | ## `maps.results` [Section titled “maps.results”](#mapsresults) **Maps — local results** — Every place ranked, per keyword and grid point. One row per place ranked at each grid point of a Maps run. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | ----------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `country` | string | yes | Country code. | | `language` | string | yes | Language code. | | `device` | string | yes | Device type. | | `keyword` | string | no | Keyword searched. | | `surface` | string | yes | Where the result appeared. | | `grid_row` | integer | yes | Grid row of the search point. | | `grid_col` | integer | yes | Grid column of the search point. | | `lat` | float | yes | Latitude of the search point. | | `lng` | float | yes | Longitude of the search point. | | `name` | string | yes | Place name. | | `position` | integer | yes | Rank of the place. | | `is_sponsored` | boolean | yes | Whether the listing was an ad. | | `ad_type` | string | yes | Kind of ad, when sponsored. | | `rating` | float | yes | Star rating. | | `review_count` | integer | yes | Number of reviews. | | `category` | string | yes | Place category. | | `address` | string | yes | Place address. | | `google_place_id` | string | yes | Google place id. | | `cid` | string | yes | Google customer id of the listing. | ## `marketplace.results` [Section titled “marketplace.results”](#marketplaceresults) **Marketplace — product results** — Every product listed, per keyword and marketplace. One row per product listed for a keyword in a Marketplace run. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | --------------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `country` | string | yes | Country code. | | `language` | string | yes | Language code. | | `keyword` | string | no | Keyword searched. | | `platform` | string | yes | Marketplace platform. | | `marketplace` | string | yes | Marketplace site. | | `type` | string | yes | Organic or sponsored. | | `placement` | string | yes | Where on the page it appeared. | | `section_position` | integer | yes | Position within its section. | | `global_position` | integer | yes | Position on the whole page. | | `title` | string | yes | Product title. | | `brand` | string | yes | Product brand. | | `seller` | string | yes | Seller. | | `product_external_id` | string | yes | The marketplace’s own product id. | | `product_url` | string | yes | Product page URL. | | `price` | float | yes | Listed price. | | `currency` | string | yes | Price currency. | | `rating` | float | yes | Star rating. | | `reviews_count` | integer | yes | Number of reviews. | | `is_tracked` | boolean | yes | Whether it is one of your tracked products. | ## `ai_visibility.responses` [Section titled “ai_visibility.responses”](#ai_visibilityresponses) **AI visibility — answers** — One row per prompt answered, with how your brand fared. One row per AI visibility check — one prompt asked on one platform — with how your own brand fared. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | ---------------------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `prompt` | string | no | Prompt asked. | | `platform` | string | no | AI platform answering. | | `location` | string | yes | Country the prompt was asked from. | | `city` | string | yes | City, when localised. | | `own_brand_mentioned` | boolean | yes | Whether your brand was mentioned. | | `own_brand_mention_count` | integer | yes | How often your brand was mentioned. | | `own_brand_position` | integer | yes | Rank of your brand among those named. | | `own_brand_sentiment` | float | yes | Sentiment toward your brand, -1 to 1. | | `own_brand_recommended` | boolean | yes | Whether your brand was recommended. | | `own_brand_recommended_rank` | integer | yes | Rank among the recommendations. | | `own_brand_sponsored` | boolean | yes | Whether your brand appeared in an ad. | | `competitor_count` | integer | yes | Competitors named in the answer. | | `response_type` | string | yes | Kind of answer. | | `status` | string | no | Check outcome. | | `error_code` | string | yes | Why the check failed, when it did. | ## `ai_visibility.mentions` [Section titled “ai_visibility.mentions”](#ai_visibilitymentions) **AI visibility — brand mentions** — Every brand named in an answer, yours and competitors. One row per brand named in an AI answer — your own and competitors’. Several rows share one answer. Schema version 1. Exported when the campaign run that produced the row finishes. | Column | Type | Nullable | Description | | ------------------ | ------------ | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `run_id` | string | no | The campaign run that produced the row. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `prompt` | string | yes | Prompt asked. | | `platform` | string | yes | AI platform answering. | | `location` | string | yes | Country the prompt was asked from. | | `domain` | string | yes | Brand domain. | | `name` | string | yes | Brand name as written. | | `count` | integer | yes | Times mentioned. | | `is_own` | boolean | yes | Whether it is your brand. | | `position` | integer | yes | Rank among the brands named. | | `sentiment` | float | yes | Sentiment toward the brand, -1 to 1. | | `recommended` | boolean | yes | Whether it was recommended. | | `recommended_rank` | integer | yes | Rank among the recommendations. | | `is_sponsored` | boolean | yes | Whether the mention was an ad. | | `ad_headline` | string | yes | Ad headline, when sponsored. | | `ad_description` | string | yes | Ad text, when sponsored. | | `attributes` | string_array | yes | Qualities the answer attributed to the brand. | | `snippet` | string | yes | Excerpt of the answer around the mention. | ## `search_history` [Section titled “search_history”](#search_history) **Search history** — Searches run with the Search Engine Proxy and Direct Search. One row per search run with the Search Engine Proxy or Direct Search. Filter by search type or to saved searches only. Schema version 1. Exported by the time the row was created. | Column | Type | Nullable | Description | | --------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `search_type` | string | no | proxy or direct. | | `project_id` | string | yes | Project the search was filed under, if any. | | `query` | string | no | Search query. | | `country` | string | yes | Country code the search ran in. | | `language` | string | yes | Language code. | | `device` | string | yes | Device type. | | `search_engine` | string | yes | Search engine. | | `city` | string | yes | City, when the search was localised. | | `google_domain` | string | yes | Search engine domain used. | | `status` | string | yes | Outcome of the search. | | `saved` | boolean | yes | Whether the search was saved. | | `page_count` | integer | yes | Result pages fetched. | ## `search_history.results` [Section titled “search_history.results”](#search_historyresults) **Search history — results** — Every result on the pages fetched by Search Engine Proxy searches. One row per result on the pages a Search Engine Proxy search fetched. Direct Search does not capture results. Filter to saved searches only. Schema version 1. Exported by the time the row was created. | Column | Type | Nullable | Description | | ------------------ | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `search_id` | string | no | The search the result belongs to. | | `project_id` | string | yes | Project the search was filed under, if any. | | `query` | string | no | Search query. | | `country` | string | yes | Country code. | | `page_number` | integer | yes | Results page. | | `type` | string | yes | Kind of result (organic, ad, …). | | `section_position` | integer | yes | Position within its section. | | `global_position` | integer | yes | Position on the whole page. | | `title` | string | yes | Result title. | | `url` | string | yes | Result URL. | | `display_url` | string | yes | URL as displayed. | | `domain` | string | yes | Result domain. | ## `alerts` [Section titled “alerts”](#alerts) **Campaign alerts** — Alerts raised on your campaigns. One row per alert raised on a campaign. Filter by tool, severity or campaign. You only receive alerts for tools you can read. Schema version 1. Exported by the time the row was created. | Column | Type | Nullable | Description | | --------------- | --------- | -------- | ------------------------------------------------- | | `row_id` | string | no | Stable id of this row; unique within the dataset. | | `measured_at` | timestamp | no | When the value was measured, in UTC. | | `tool` | string | no | Tool the campaign belongs to. | | `campaign_id` | string | no | Campaign id. | | `campaign_name` | string | no | Campaign name at export time. | | `project_id` | string | yes | Project the campaign belongs to, if any. | | `type` | string | no | What the alert is about. | | `severity` | string | no | Alert severity. | | `metadata` | json | yes | Details of the alert. | ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Webhook destination](/exports/webhooks/) * [Cloud storage buckets](/exports/amazon-s3/) * [File formats](/exports/formats/) * [BigQuery](/exports/bigquery/) * [Google Sheets](/exports/google-sheets/) # File formats > CSV, Excel, JSON Lines, SQL for PostgreSQL and MySQL, and Parquet — what each export format contains, how values are written, and how to load SQL and Parquet files. Every file-based [export](/exports/overview/) — a download, an email, a Google Drive folder, a cloud storage bucket or an SFTP server — writes **one file per dataset per run**, in the format you choose: | Format | Extension | Best for | | -------------------- | ---------- | --------------------------------------------------------------- | | **CSV** | `.csv` | Spreadsheets and any tool that imports CSV | | **Excel** | `.xlsx` | People who open the file in Excel, Numbers or Google Sheets | | **JSON Lines** | `.ndjson` | Developers, scripts and log pipelines | | **SQL (PostgreSQL)** | `.sql` | Loading into PostgreSQL, SQLite or DuckDB with one command | | **SQL (MySQL)** | `.sql` | Loading into MySQL or MariaDB with one command | | **Parquet** | `.parquet` | Data lakes and warehouses — typed, compressed and fast to query | A cloud storage bucket takes every format except Excel. Parquet is included on the Agency plan; the others on Team and Agency — see [plans and limits](/exports/overview/#plans-and-limits). Every file has the columns of its [dataset](/exports/datasets/) in reference order — or, if you [picked columns](/exports/overview/#choosing-columns), only those, plus `row_id` and `measured_at`, which are always included. ## Compression [Section titled “Compression”](#compression) * **CSV, JSON Lines and SQL** are text. In buckets, on Google Drive and on SFTP servers they are gzipped and end in `.gz` — `….csv.gz`, `….ndjson.gz`, `….sql.gz`. Downloads (including email links) arrive as the plain file; your browser decompresses them. * **Excel and Parquet** are compressed by design, so they are never gzipped again: always `.xlsx` and `.parquet`. ## CSV [Section titled “CSV”](#csv) * RFC 4180, UTF-8, a header row, lines ending in CRLF. * Numbers and `true` / `false` as themselves; timestamps as ISO-8601 in UTC, e.g. `2026-09-30T06:02:14.512Z`. * List columns (`string_array`) are joined with `|`; JSON columns are written as JSON text. * An empty value is an empty cell. * **Formula protection.** A text value starting with `=`, `+`, `-`, `@`, a tab or a carriage return is prefixed with `'`, so a spreadsheet never runs it as a formula. Numbers are never changed — `-3` stays `-3`. If you load CSV into a database and need text byte-for-byte, use JSON Lines, SQL or Parquet instead. ## Excel [Section titled “Excel”](#excel) An `.xlsx` workbook per dataset, with the header row frozen. * **One sheet per dataset**, named after it with `.` replaced by a space — `position_tracker results`. Excel allows 1,048,576 rows per sheet; a longer file continues on `position_tracker results (2)` and so on. * **Real cell types.** Numbers are numbers, `true` / `false` are Excel booleans, and timestamps are real Excel dates shown as `yyyy-mm-dd hh:mm:ss` — **in UTC**, since Excel dates carry no time zone. * **Text always stays text.** A value that looks like a formula is stored as a text cell, not run and not prefixed. * **Long values are cut** at Excel’s limit of 32,767 characters per cell. Long AI answers can reach it; use JSON Lines or Parquet when you need the full text. * **Very large integers** — above 2^53 (9,007,199,254,740,992), which Excel would round — are written as text so no digit is lost. * List columns are joined with `|`; JSON columns are JSON text. ## JSON Lines [Section titled “JSON Lines”](#json-lines) One JSON object per line (also called NDJSON), UTF-8. * Every exported column is present in every object — `null` when empty. * Numbers and booleans are JSON numbers and booleans; timestamps are ISO-8601 strings in UTC; list columns are arrays of strings; JSON columns are objects. ```json { "row_id": "…", "measured_at": "2026-09-30T06:02:14.512Z", "keyword": "coffee beans", "position": 3 } ``` ## SQL (PostgreSQL and MySQL) [Section titled “SQL (PostgreSQL and MySQL)”](#sql-postgresql-and-mysql) A SQL file is a script that loads one dataset into a table of your own database. Run it and the table is created if it doesn’t exist, and the rows are inserted. ```sql -- position_tracker.results, exported by iSearchFrom. Columns: 18. BEGIN; CREATE TABLE IF NOT EXISTS "position_tracker_results" ( "row_id" TEXT PRIMARY KEY, "measured_at" TIMESTAMPTZ NOT NULL, ... ); INSERT INTO "position_tracker_results" ("row_id", "measured_at", ...) VALUES (...), (...) ON CONFLICT ("row_id") DO NOTHING; COMMIT; ``` * **The table** is named after the dataset with `.` replaced by `_`: `position_tracker_results`, `ai_visibility_mentions`, `alerts`. * **`row_id` is the primary key.** Every insert skips rows the table already has, so you can load overlapping files — a backfill, a retried run, the same file twice — without creating duplicates. * **One transaction.** The whole file loads, or none of it does. * Rows are inserted **500 per `INSERT`**, small enough for any client’s packet limit. * List columns and JSON columns are stored as `JSON`. ### Loading a SQL file [Section titled “Loading a SQL file”](#loading-a-sql-file) Files from a bucket, Google Drive or an SFTP server are gzipped — decompress on the fly: ```sh # PostgreSQL psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f position_tracker-results.sql gunzip -c position_tracker-results_2026-09-30_.sql.gz | psql "$DATABASE_URL" -v ON_ERROR_STOP=1 # SQLite 3.24 or later (PostgreSQL file) sqlite3 isearchfrom.db < position_tracker-results.sql # DuckDB (PostgreSQL file) duckdb isearchfrom.duckdb < position_tracker-results.sql # MySQL or MariaDB (MySQL file) mysql isearchfrom < position_tracker-results.sql gunzip -c position_tracker-results_2026-09-30_.sql.gz | mysql isearchfrom ``` ### PostgreSQL file [Section titled “PostgreSQL file”](#postgresql-file) Plain types — `TEXT`, `BIGINT`, `DOUBLE PRECISION`, `BOOLEAN`, `TIMESTAMPTZ`, `JSON` — and `ON CONFLICT (row_id) DO NOTHING`, which PostgreSQL, **SQLite 3.24+** and **DuckDB** all read, so one file serves all three. Text uses standard SQL quoting, which needs `standard_conforming_strings` to be `on` — the PostgreSQL default since 9.1. SQLite and DuckDB always behave this way. ### MySQL file [Section titled “MySQL file”](#mysql-file) For MySQL and MariaDB: `utf8mb4` throughout, an InnoDB table, `ON DUPLICATE KEY UPDATE` to skip existing rows. * Timestamps are `DATETIME(6)` holding **UTC** — no 2038 limit, and no conversion by the session’s time zone. * Text is `LONGTEXT`; `row_id` is `VARCHAR(191)`, the longest `utf8mb4` key MySQL can index. * List and JSON columns are `JSON`; booleans are `BOOLEAN`. * A value containing a backslash is written as a hex literal, so the file loads the same with or without the server’s `NO_BACKSLASH_ESCAPES` mode. ### Changing columns [Section titled “Changing columns”](#changing-columns) `CREATE TABLE IF NOT EXISTS` never changes a table that already exists. If an earlier file created it with different columns — because you changed the [column pick](/exports/overview/#choosing-columns) or the dataset [gained a column](/exports/datasets/#stability) — adjust the table before loading the new file: ```sql -- A column the new file has and the table doesn't: ALTER TABLE "position_tracker_results" ADD COLUMN "description" TEXT; -- A required column the new file no longer carries (PostgreSQL): ALTER TABLE "position_tracker_results" ALTER COLUMN "campaign_name" DROP NOT NULL; ``` Or load into a new table and keep the same pick from then on. ## Parquet [Section titled “Parquet”](#parquet) Parquet is a columnar file with types built in — the natural format for a data lake. Files are Snappy-compressed, with one row group per 2,000 rows, and read without configuration by DuckDB, pandas, pyarrow, Polars, BigQuery, Snowflake, Redshift, Databricks and Spark. | Dataset type | Parquet type | | -------------- | -------------------------------------------- | | `string` | `BYTE_ARRAY` (`UTF8`) | | `integer` | `INT64` | | `float` | `DOUBLE` | | `boolean` | `BOOLEAN` | | `timestamp` | `INT64` `TIMESTAMP(MICROS)`, adjusted to UTC | | `json` | `BYTE_ARRAY` (`JSON`) — JSON text | | `string_array` | `LIST` of `UTF8` | Columns that are never empty in the [dataset reference](/exports/datasets/) are `REQUIRED`; every other column is `OPTIONAL`. A reader that doesn’t understand the `JSON` annotation sees those columns as text. ```sql -- DuckDB SELECT keyword, position, measured_at FROM read_parquet('position_tracker-results_*.parquet') ORDER BY measured_at DESC; ``` ```python # pandas (with pyarrow installed) import pandas as pd df = pd.read_parquet("position_tracker-results_2026-09-30_.parquet") ``` For Snowflake, Redshift, Databricks and more, see [warehouse recipes](/exports/warehouse-recipes/). ## Duplicates [Section titled “Duplicates”](#duplicates) Delivery is at least once: a backfill, or a failed run followed by a successful one, can deliver a row again. It always has the same `row_id`. SQL files skip such rows by themselves; for every other format, de-duplicate on `row_id` when you load. ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Dataset reference](/exports/datasets/) * [Warehouse recipes](/exports/warehouse-recipes/) # Google Drive > Deliver dated export files — Excel, CSV, JSON Lines, SQL or Parquet — into a folder of your Google Drive on every run. A Google Drive destination adds **dated files to a folder in your Google Drive** on every run: one file per dataset, in the [format](/exports/formats/) you choose. It suits teams who work from Drive — open an Excel file straight from the folder, share the folder with a client, or let another tool pick the files up. Google Drive destinations are included on the Team and Agency plans. ## What you need [Section titled “What you need”](#what-you-need) A Google account connected under **Settings → Integrations** with **Google Drive** access. Google Drive and [Google Sheets](/exports/google-sheets/) use the same Google permission, so an account connected for one works for the other. iSearchFrom asks Google only for access to **files it creates**. It cannot open, list or change any other file or folder in your Drive — which is why it always creates its own folder rather than writing into one you already have. ## Settings [Section titled “Settings”](#settings) | Setting | Notes | | ------------------ | -------------------------------------------------------------------------------------------------------- | | **Google account** | The connected account whose Drive receives the files. **Use another Google account** connects a new one. | | **Folder name** | `iSearchFrom exports` by default. The folder is created with this name on the first run. | | **Format** | CSV, Excel, JSON Lines, SQL or Parquet, as your plan allows. | **Test connection** checks that the account is connected and, once the folder exists, that it can still reach it. ## How it works [Section titled “How it works”](#how-it-works) * **The first run creates the folder** at the top of the connected account’s Drive. Every run after that adds its files to the same folder. You can rename it, move it or share it from Drive like any other folder; the export keeps finding it. * **If the folder is deleted or moved to the bin**, the next run creates a new one. * **One file per dataset per run**, named after the dataset, the day and the run: ```text __. ``` For example: ```text position_tracker-results_2026-09-30_0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.xlsx alerts_2026-09-30_0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz ``` * `` is the dataset id with `.` replaced by `-`. * The date is the UTC day the run’s window ends. * CSV, JSON Lines and SQL files are gzipped (`.csv.gz`, `.ndjson.gz`, `.sql.gz`); Excel and Parquet files are not. Drive can’t preview a `.gz` file — for people who open files in Drive, choose **Excel**. * **A retried run replaces its own file** instead of adding a second copy. Files count towards the connected account’s Google storage. iSearchFrom never deletes files it has delivered — clear out old ones when you no longer need them. ## Duplicates [Section titled “Duplicates”](#duplicates) Each run delivers only what is new since the last successful one, so the files add up to your complete history. A backfill with **Run now** re-sends an older window as new files, and those rows can repeat rows you already have. Every row keeps its `row_id`, so de-duplicate on it when you combine files. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Code | Likely cause | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `reconnect_required` | The Google connection expired or was revoked. Reconnect it under **Settings → Integrations**; the destination resumes by itself. | | `access_denied` | Google refused the write — for example, the account’s storage is full. | | `not_found` | The folder could not be found. Check that the account can still open it. | See [all error codes](/exports/overview/#error-codes). ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [File formats](/exports/formats/) * [Google Sheets](/exports/google-sheets/) # Google Sheets > Keep a Google Sheets spreadsheet filled with your exported data — one tab per dataset, new rows appended on every run. A Google Sheets destination keeps a spreadsheet in your Google Drive up to date: one tab per dataset, with new rows appended on every run. It is the lightest way to chart results, build a client report, or share data with people who don’t use iSearchFrom. ## What you need [Section titled “What you need”](#what-you-need) A Google account connected under **Settings → Integrations** with **Google Sheets** access. iSearchFrom asks Google only for access to **files it creates**. It cannot open, list or change any other file in your Drive — which is why it always creates its own spreadsheet rather than writing into one you already have. ## How it works [Section titled “How it works”](#how-it-works) * **The first run creates the spreadsheet** in the connected account’s Drive, named after the destination. Share it from Google Sheets like any other file. * **One tab per dataset**, named after it — `position_tracker.results`, `alerts` and so on. If a tab with that name already exists, the new one gets a number: `alerts (2)`. * **The first row is the header.** It is frozen and rewritten on every run, so it always matches the dataset’s current columns. New columns appear on the right. * **Every run appends** the new rows below the existing ones. * **With [picked columns](/exports/overview/#choosing-columns)**, a tab holds only those. If you later remove a picked column or the order changes, the next run starts a new tab — `alerts (2)` — rather than writing rows under a header they don’t match. Columns added after the ones already picked go on the right of the same tab. Values are written as plain values, never as formulas: numbers and true/false are native cells, timestamps are ISO-8601 text in UTC, lists are joined with `|`, and JSON columns are JSON text. ## The export’s tabs are managed [Section titled “The export’s tabs are managed”](#the-exports-tabs-are-managed) The tabs the export creates belong to the export. Before each write, a tab is trimmed back to the rows that finished runs left in it — that is how rows from a run that failed half-way are removed before they could be written twice. So, in those tabs: * **Don’t sort, insert or delete rows, or type below the data.** Anything below the last exported row is removed, and sorted rows no longer line up with what the export expects. * **Don’t rename or edit the header.** It is rewritten every run. * **Do add your own tabs** — charts, pivot tables, `QUERY()`, `FILTER()` and `VLOOKUP()` over the export’s tabs all keep working as rows are added. Filter views are fine too. If you delete an export tab, the next run creates it again, starting empty from that run onwards. ## Limits [Section titled “Limits”](#limits) Google allows **10 million cells per spreadsheet**, counting every tab — including yours — by its full grid size. A run that would pass the limit fails with `sheet_full` and the destination is marked **needs attention**. To continue: * create a new Google Sheets destination, which starts a new spreadsheet; * narrow the selection — fewer datasets or campaigns; or * delete your own large tabs from the spreadsheet, then resume the destination. A single cell holds at most 50,000 characters; longer values are cut to fit. For long-running, high-volume history, a [BigQuery](/exports/bigquery/) or [cloud storage](/exports/amazon-s3/) destination is the better home. ## Duplicates [Section titled “Duplicates”](#duplicates) A backfill with **Run now** re-sends an older window and appends those rows again. Every row keeps its `row_id` (the first column), so you can spot and remove repeats — for example with `=UNIQUE()` over the tab in a tab of your own. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Code | Likely cause | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `reconnect_required` | The Google connection expired or was revoked. Reconnect it under **Settings → Integrations**; the destination resumes by itself. | | `not_found` | The spreadsheet was deleted. Restore it from the bin, or create a new destination. | | `access_denied` | The connected account can no longer edit the spreadsheet. | | `sheet_full` | The spreadsheet reached 10 million cells — see [Limits](#limits). | See [all error codes](/exports/overview/#error-codes). ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Dataset reference](/exports/datasets/) # Exporting your data > Take your rankings, ads, Maps, marketplace, AI visibility, search history and alerts out of iSearchFrom — as a one-off file, or on a schedule to email, a webhook, Google Sheets or Drive, a cloud storage bucket, BigQuery or an SFTP server. Everything iSearchFrom measures for you can leave the platform in a documented, stable shape. Export a file once when you need it, or save a **destination** that keeps delivering new data on a schedule — to your team’s inboxes, your own endpoint, a Google Drive folder, a storage bucket, an SFTP server, a data warehouse or a spreadsheet. ## What you can export [Section titled “What you can export”](#what-you-can-export) Data is exported as **datasets** — one table-shaped stream per kind of measurement: | Dataset | What each row is | | -------------------------- | --------------------------------------------------------------------- | | `position_tracker.results` | A ranking position found, per keyword and domain | | `position_tracker.checks` | A keyword check with your best position, including checks that failed | | `ad_tracker.ads` | A paid ad seen, per keyword | | `maps.results` | A place ranked, per keyword and grid point | | `marketplace.results` | A product listed, per keyword and marketplace | | `ai_visibility.responses` | A prompt answered, with how your brand fared | | `ai_visibility.mentions` | A brand named in an answer — yours or a competitor’s | | `search_history` | A search run with the Search Engine Proxy or Direct Search | | `search_history.results` | A result on a page fetched by a Search Engine Proxy search | | `alerts` | An alert raised on one of your campaigns | Every row of every dataset starts with the same two columns: * **`row_id`** — a stable id, unique within its dataset. If a row ever reaches you twice, it has the same `row_id` both times, so you can always de-duplicate on it. * **`measured_at`** — when the value was measured, in UTC. Warehouses partition on it. The full column list for every dataset is in the [dataset reference](/exports/datasets/). Columns are a contract Columns are only ever **added** — never renamed, removed or retyped. Each dataset carries a `schema_version` that goes up when it gains a column. Build your pipelines to tolerate new columns and they will not break. ### Choosing what goes into an export [Section titled “Choosing what goes into an export”](#choosing-what-goes-into-an-export) An export is a **selection**: * **Scope** — the whole organization, or up to 50 specific projects. * **Datasets** — one or more of the datasets above, each picked once. * **Filters per dataset:** * Campaign datasets — specific campaigns (up to 200 per dataset), or all of them. “All” is resolved at every run, so a campaign created later joins by itself. * `search_history` — only proxy or only direct searches, and/or only saved searches. * `search_history.results` — only saved searches. * `alerts` — by tool, by severity, and/or by campaign. * **Columns per dataset** — every column, or only the ones you pick. See [choosing columns](#choosing-columns). You can only export what you can already see. Each dataset needs the matching read permission — Position tracker, Maps, Search history and so on — and the tool must be on your plan. With [project-level access](/team-account/access-control/), “the whole organization” means the projects you have access to. ### Choosing columns [Section titled “Choosing columns”](#choosing-columns) By default an export carries every column of each dataset. To send fewer, press **Choose columns** next to a dataset and untick what you don’t need; **Export every column again** undoes it. * **`row_id` and `measured_at` are always included**, whatever you pick, so every file can still be de-duplicated and partitioned. * Picked columns keep their [dataset](/exports/datasets/) order. * With a pick, **columns the dataset gains later are not added** — the export sends exactly what you chose. With no pick, new columns arrive by themselves. What a pick means at each destination: | Destination | With picked columns | | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Files (download, email, bucket, Google Drive, SFTP) | Each file holds only the picked columns. A bucket’s [manifest](/exports/amazon-s3/#the-manifest) lists them | | [Webhook](/exports/webhooks/) | Each row object carries only the picked columns | | [BigQuery](/exports/bigquery/) | Tables keep every column; the columns you left out are `NULL` in the rows loaded | | [Google Sheets](/exports/google-sheets/) | A tab holds the picked columns. Removing or reordering them later starts a new tab, e.g. `alerts (2)`; columns added after them go on the right | | [SQL files](/exports/formats/#sql-postgresql-and-mysql) | `CREATE TABLE` names only the picked columns — see [changing columns](/exports/formats/#changing-columns) | ## One-off downloads [Section titled “One-off downloads”](#one-off-downloads) The quickest export: choose a selection, a time window and a format, and download the result. * **Window** — a fixed date range, or the last *N* days; at most 366 days either way. * **Format** — any [file format](#formats) your plan includes: CSV, Excel, JSON Lines, SQL or Parquet. * **Result** — one file per dataset. A dataset with no rows in the window still gets a file (in CSV, just the header row). Only the person who requested a download can fetch its files. Each download link is valid for five minutes — request a fresh one whenever you need it. The files themselves are kept for **7 days**. ## Destinations [Section titled “Destinations”](#destinations) A destination is a saved export: where the data goes, what goes into it, and optionally a schedule. | Destination | Formats | Best for | | ----------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------- | | **Download** | Every file format | A file right now; one-off only, never saved | | **Email** | Every file format | Regular files for teammates who don’t run a pipeline | | **[Webhook](/exports/webhooks/)** | JSON (signed batches) | Feeding your own service, an automation tool, or a queue | | **[Google Sheets](/exports/google-sheets/)** | Rows in a spreadsheet | Lightweight reporting, charts and sharing with people outside the platform | | **[Google Drive](/exports/google-drive/)** | Every file format | Dated files in a Drive folder, for people who work from Drive | | **[Cloud storage bucket](/exports/amazon-s3/)** | Parquet, CSV, JSON Lines, SQL | A data lake, and the source for Snowflake, Redshift, Databricks and DuckDB | | **[BigQuery](/exports/bigquery/)** | Loaded as tables | Querying and joining your data in Google’s warehouse | | **[SFTP server](/exports/sftp/)** | Every file format | Dropping files where an existing import job or partner already looks | A **cloud storage bucket** can be Amazon S3, Google Cloud Storage, Cloudflare R2, Wasabi, Backblaze B2, DigitalOcean Spaces, MinIO or any other S3-compatible store — see [providers](/exports/amazon-s3/#providers). An **email** destination sends to organization members (up to 20). The email carries a link to the files in iSearchFrom — never the file itself — so a forwarded message opens nothing without a sign-in. A recipient only gets the link while they can still see all of the data in the export themselves. Files are kept for 7 days, like downloads. Before saving a webhook, bucket, BigQuery, Google Sheets, Google Drive or SFTP destination you can press **Test connection**: a small check that confirms the address, credentials and permissions work. For an SFTP server it also shows the server’s host key, so you can check it and pin it. ## Formats [Section titled “Formats”](#formats) Every file-based destination lets you choose the file format: | Format | What it is | Use it for | | -------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | **CSV** | Comma-separated text with a header row | Spreadsheets, and any tool that imports CSV | | **Excel** | An `.xlsx` workbook, one sheet per dataset, with real numbers and dates | People who open the file in Excel, Numbers or Google Sheets | | **JSON Lines** | One JSON object per line | Developers, scripts and log pipelines | | **SQL (PostgreSQL)** | A script that creates the table and inserts the rows; also SQLite and DuckDB | Loading straight into your own database with `psql`, `sqlite3` or `duckdb` | | **SQL (MySQL)** | The same, in MySQL and MariaDB syntax | Loading with `mysql` | | **Parquet** | A compressed, typed columnar file | Data lakes and warehouses: Snowflake, Redshift, Databricks, BigQuery, DuckDB, pandas | Which destinations take which format: | Destination | CSV | Excel | JSON Lines | SQL | Parquet | | ----------------------------------- | --- | ----- | ---------- | --- | ------- | | Download, Email, Google Drive, SFTP | ✓ | ✓ | ✓ | ✓ | ✓ | | Cloud storage bucket | ✓ | | ✓ | ✓ | ✓ | A webhook always receives JSON, BigQuery always loads rows into tables, and Google Sheets always writes rows into tabs — there is no format to choose for those. CSV, JSON Lines and SQL files are gzipped (`.gz`) in buckets, on Google Drive and on SFTP servers; downloads arrive uncompressed. Excel and Parquet compress themselves and are never gzipped again. Which formats you can choose also depends on your [plan](#plans-and-limits). The [formats guide](/exports/formats/) covers each format in depth, including how to load SQL files. ## Starter templates [Section titled “Starter templates”](#starter-templates) When you add a destination, **Start from a template** fills in the destination, data, format and schedule for a common setup. Everything stays editable before you save. | Template | Delivers | Format | When | | ----------------------------------- | ------------------------------------------------- | ------- | ------------------ | | **Weekly rankings by email** | Position tracker checks to your team | Excel | Mondays at 07:00 | | **Daily rankings in Google Sheets** | Every keyword check appended to a spreadsheet | — | Every day at 06:00 | | **Weekly AI visibility to Drive** | AI answers and brand mentions | Excel | Mondays at 07:00 | | **Alerts to a webhook** | New alerts, for Slack, Zapier or your own service | JSON | Every hour | | **Sync to your data lake** | All tracking data to your bucket | Parquet | Every hour | | **Sync to BigQuery** | All tracking data loaded into your warehouse | — | Every hour | Only templates your plan and role allow are shown. Or choose where to send it and set everything up yourself. ### Who a destination runs as [Section titled “Who a destination runs as”](#who-a-destination-runs-as) A saved destination acts with the access of **the member who last saved it**. Every run re-checks that access: a dataset that member can no longer read is skipped (the run lists it and why), and if they leave the organization the destination stops until someone else edits and saves it. Whoever saves a destination becomes the member it runs as. ## Schedules and incremental delivery [Section titled “Schedules and incremental delivery”](#schedules-and-incremental-delivery) A destination can run on a **schedule** — every day or every week at the time and in the time zone you choose, or every hour (through the API, any cron expression) — or **Only when I run it**, when someone presses **Run now**. Team schedules run at most daily. Agency adds hourly schedules for destinations a machine reads: webhooks, cloud storage buckets, BigQuery and SFTP. An hourly destination runs at a minute we pick for it, so syncs across the platform are spread over the hour. Scheduled destinations are **incremental**: each run delivers what is new since the last successful run, so your destination builds up a complete history without repeats. A scheduled run that finds nothing new sends nothing: no empty file, email or webhook call. It still shows in **Recent exports** as “Nothing new”. A run you start yourself always delivers, even when it is empty. * **The first run** reaches back as far as you choose when saving the destination — 7 days by default, up to 365. * **A five-minute settle lag.** Each run stops five minutes before it starts, so data still being written is picked up by the next run instead of being missed. * **Campaign rows arrive with their run.** Rows from a campaign run are exported once that run has finished, all together — a long run is never split across two exports or missed between them. Search history and alerts are exported by the time they were created. * **One run at a time** per destination. If the previous run is still going, the next scheduled one waits for the following slot. **Run now** runs the same incremental export immediately. It can also take a **fixed date range** — a backfill — to re-send an older window. A backfill does not move the destination’s incremental position, so the schedule carries on where it was; the rows it re-sends can repeat rows the destination already has. A backfill that fails leaves the destination’s status alone; only the incremental runs decide whether it needs attention. If a dataset in the selection stops being readable — the member it runs as lost access, or the plan dropped that tool — incremental runs stop with **Needs attention** instead of skipping it, so no window is lost. Restore the access or the plan and resume it, or remove that dataset and save; it carries on from where it stopped. De-duplicate on `row_id` Delivery is **at least once**. A backfill, or a run that failed half-way and was then covered by the next one, can deliver a row a second time. It always carries the same `row_id`, so key your tables and upserts on `(dataset, row_id)`. This matters most where files and rows are only ever added — BigQuery tables, bucket and SFTP folders, Google Drive, and webhook receivers. Google Sheets tidies its own tab after a retry. ### Stopping an export [Section titled “Stopping an export”](#stopping-an-export) You can cancel an export that is still running. It stops before its next batch; anything it had already delivered stays delivered. ## When something goes wrong [Section titled “When something goes wrong”](#when-something-goes-wrong) Temporary problems — the destination timing out, a rate limit, a server error on its side — are retried automatically. A run that still fails is marked **failed** with an error code, and nothing is lost: the destination’s incremental position only moves on success, so the next run delivers the same data again. A destination has a **status**: | Status | Meaning | What resumes it | | ------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | **Active** | Running normally | — | | **Paused** | Paused by a person, or automatically after 10 failed runs in a row | Resume it | | **Needs attention** | The destination refused the data — a wrong bucket, a deleted sheet, a rejected request | Fix the settings and save; it runs again | | **Needs reconnect** | The connected Google account must be connected again | Reconnect it under **Settings → Integrations**; the destination resumes by itself | | **Suspended** | Your plan no longer includes it, or the member it runs as has left | Resumes by itself when the plan allows it again; for a departed member, edit and save it | When a destination needs a person — or after three failed runs in a row — the organization’s owners and the member it runs as are notified in the app and by email, at most once a day per destination. ### Error codes [Section titled “Error codes”](#error-codes) Each failed run, test and skipped dataset reports one of these codes: | Code | What happened | What to do | | ------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | | `destination_unreachable` | The destination could not be reached, timed out, or had a server error | Usually temporary and retried automatically. If it persists, check the URL or endpoint is public and up | | `destination_rejected` | The destination refused the data | Check the destination’s settings; for a webhook, your endpoint answered with a `4xx` or a redirect | | `access_denied` | Credentials were refused, or the member lost access to the selected data | Check keys and permissions at the destination, or the member’s roles in iSearchFrom | | `reconnect_required` | The connected Google account’s access expired or was revoked | Reconnect it under **Settings → Integrations** | | `not_found` | The bucket, dataset, spreadsheet or endpoint no longer exists | Recreate it, or point the destination somewhere that exists | | `too_large` | The export has more rows than one run may carry on your plan | Narrow the selection or the window; split a large backfill into shorter ranges | | `host_key_changed` | An SFTP server presented a different host key than the one pinned | If the change was expected, check the new key and pin it — see [host keys](/exports/sftp/#host-keys) | | `sheet_full` | The spreadsheet reached Google’s 10-million-cell limit | See [Google Sheets limits](/exports/google-sheets/#limits) | | `selection_empty` | Nothing in the selection can be exported any more | Check the campaigns and projects still exist and the member can read them | | `plan_limit` | The current plan does not include this destination, format or schedule | Change the destination or the plan; suspended destinations resume by themselves on upgrade | | `owner_removed` | The member the destination runs as has left the organization | Edit and save the destination as a current member; it runs again | | `internal_error` | Something went wrong on our side | Retried automatically; contact support if it keeps happening | ## Plans and limits [Section titled “Plans and limits”](#plans-and-limits) Exports are part of the **Team** and **Agency** plans. Starter and Pro don’t include exports. | | Team | Agency | | ---------------------- | ----------------------------------------------------- | -------------------------------------------------------------- | | **Destinations** | Download, Email, Webhook, Google Sheets, Google Drive | Everything in Team, plus cloud storage buckets, BigQuery, SFTP | | **Formats** | CSV, Excel, JSON Lines, SQL (PostgreSQL and MySQL) | Everything in Team, plus Parquet | | **Schedules** | Daily or weekly | Adds hourly for webhooks, buckets, BigQuery and SFTP | | **Saved destinations** | 10 | Unlimited | | **Rows per run** | 250,000 | 1,000,000 | A run with more rows than your plan allows fails with `too_large` — narrow the selection, or split a backfill into shorter windows. After a downgrade nothing you set up is deleted: destinations the new plan does not include are suspended with their settings intact, and resume by themselves after an upgrade. If you are over the plan’s destination count, the oldest destinations keep running. ## Permissions [Section titled “Permissions”](#permissions) Exports have their own **Export** permission in [roles](/team-account/roles-permissions/): | Action | Allows | | ---------- | -------------------------------------------------------------------------------------------------------------------------- | | **Read** | One-off downloads of data you can already see; seeing your own exports | | **Create** | Saving destinations and testing connections | | **Update** | Editing, pausing and resuming destinations, **Run now**, rotating a webhook secret, seeing and stopping everyone’s exports | | **Delete** | Deleting a destination and its run history | Owners and admins hold all four. Members can download what they can see; sending data out of the platform on a schedule is an administrative decision. ## Retention [Section titled “Retention”](#retention) * **Downloadable files** (downloads and email exports) are kept for **7 days**. * **Run history** is kept for **180 days**. * Data already delivered to your destination is yours — iSearchFrom never deletes or changes it. ## Using the API [Section titled “Using the API”](#using-the-api) The export endpoints live under `https://api.isearchfrom.com/v1/organizations/{organizationId}/exports`: | Method | Path | Does | | -------- | ------------------------------------- | --------------------------------------------------------------------------- | | `GET` | `/catalog` | Datasets, destinations and formats available to you, and your plan’s limits | | `POST` | `/runs` | Start a one-off download | | `GET` | `/runs`, `/runs/{runId}` | List exports, or get one | | `GET` | `/runs/{runId}/download?dataset=…` | A five-minute link to one dataset’s file | | `POST` | `/runs/{runId}/cancel` | Stop an export | | `GET` | `/destinations`, `/destinations/{id}` | List destinations, or get one | | `POST` | `/destinations` | Save a destination | | `POST` | `/destinations/test` | Test connection details before saving them | | `POST` | `/destinations/{id}/test` | Test a saved destination | | `PATCH` | `/destinations/{id}` | Change a destination | | `PATCH` | `/destinations/{id}/status` | Pause or resume | | `POST` | `/destinations/{id}/runs` | Run now, optionally with a fixed backfill window | | `POST` | `/destinations/{id}/rotate-secret` | Replace a webhook’s signing secret | | `DELETE` | `/destinations/{id}` | Delete a destination and its run history | Signed-in members only Export endpoints accept a signed-in member’s session, not [API keys](/reference/authentication-api-keys/). A saved destination acts as the member who saved it, and an API key is not a member. ## Related docs [Section titled “Related docs”](#related-docs) * [Dataset reference](/exports/datasets/) * [File formats](/exports/formats/) * [Webhook destination](/exports/webhooks/) * [Cloud storage buckets](/exports/amazon-s3/) * [BigQuery](/exports/bigquery/) * [Google Sheets](/exports/google-sheets/) * [Google Drive](/exports/google-drive/) * [SFTP server](/exports/sftp/) * [Warehouse recipes](/exports/warehouse-recipes/) * [Roles and Permissions](/team-account/roles-permissions/) # SFTP server > Upload export files to your own SFTP server on a schedule — settings, password or key sign-in, host key pinning, and how files appear safely. An SFTP destination uploads files to **your own server** over SFTP: one file per dataset per run, in the [format](/exports/formats/) you choose. Use it when an existing import job, an ETL tool or a partner already picks files up from a server. SFTP destinations are part of the [Agency plan](/exports/overview/#plans-and-limits). ## Settings [Section titled “Settings”](#settings) | Setting | Notes | | ------------------------ | ------------------------------------------------------------------------------------------- | | **Host** | The server’s host name or IP address. It must be reachable from the internet. | | **Port** | `22` by default. | | **Username** | The account to sign in as. | | **Folder on the server** | An absolute path starting with `/`, e.g. `/uploads/isearchfrom`. It must already exist. | | **Sign in with** | **Password**, or **Private key**. | | **Password** | Stored encrypted and never shown again. | | **Private key** | The full private key, in OpenSSH or PEM format, **without a passphrase**. Stored encrypted. | | **Host key fingerprint** | Optional. The server’s key, like `SHA256:…`. See [host keys](#host-keys). | | **Format** | CSV, Excel, JSON Lines, SQL or Parquet, as your plan allows. | When you edit a saved destination, leave the password or private key empty to keep the saved one. **Test connection** signs in, checks that the folder can be read, and shows **The server’s key**. We recommend a dedicated account that can only write to the export folder — a chrooted SFTP-only user is ideal. For key sign-in, create a key pair just for iSearchFrom and add its public key to that account’s `~/.ssh/authorized_keys`: ```sh ssh-keygen -t ed25519 -N "" -C isearchfrom-exports -f isearchfrom_exports # Paste the contents of isearchfrom_exports (the private key) into iSearchFrom. # Add isearchfrom_exports.pub to the server account's ~/.ssh/authorized_keys. ``` ## Host keys [Section titled “Host keys”](#host-keys) A server proves who it is with its **host key**. Pinning that key means a run only ever uploads to your real server — never to anything that has taken over its address. * **Pin it yourself** — paste the fingerprint into **Host key fingerprint**. On the server: ```sh ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` or from any machine that can reach it: ```sh ssh-keyscan -p 22 sftp.example.com | ssh-keygen -lf - ``` Use the `SHA256:…` value for the key type your server offers (usually ED25519). * **Pin from the test** — press **Test connection**, compare **The server’s key** with the one your server administrator gives you, and press **Pin this key**. * **Trust on first use** — leave the field empty. The key the server presents on the first successful connection is pinned automatically. From then on, a server presenting a different key is refused: the run fails with `host_key_changed` and the destination needs attention. If the key changed on purpose — a rebuilt or migrated server — check the new key, put it in **Host key fingerprint** and save. If you didn’t expect a change, find out why before trusting the new key. ## What gets written [Section titled “What gets written”](#what-gets-written) Files go straight into the folder, named after the dataset, the day and the run: ```text /__. ``` For example: ```text /uploads/isearchfrom/position_tracker-results_2026-09-30_0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz /uploads/isearchfrom/alerts_2026-09-30_0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.csv.gz ``` * `` is the dataset id with `.` replaced by `-`; the date is the UTC day the run’s window ends. * CSV, JSON Lines and SQL files are gzipped (`.gz`); Excel and Parquet files are not. * **Files appear complete.** Each file is uploaded under a temporary hidden name — `..part` — and renamed to its real name only once it is complete. Whatever picks files up never reads half a file; just have it ignore names starting with `.` or ending in `.part`. * **A retried run replaces its own file** instead of adding a second copy. iSearchFrom never deletes delivered files. Clear out or move old files on your side. ## Network and security [Section titled “Network and security”](#network-and-security) * The server must be reachable from the internet. Private and internal addresses — `10.x`, `192.168.x`, `localhost` and the like — are refused. * Passwords and private keys are encrypted at rest and never shown again after saving. ## Duplicates [Section titled “Duplicates”](#duplicates) Each run delivers only what is new since the last successful one. A backfill with **Run now** re-sends an older window as new files, which can repeat rows you already have. Every row keeps its `row_id` — de-duplicate on it when you load. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Code | Likely cause | | ------------------------- | ----------------------------------------------------------------------------------------------------- | | `host_key_changed` | The server presented a different host key than the pinned one. See [host keys](#host-keys). | | `access_denied` | Wrong username, password or key, or the account can’t write to the folder. | | `not_found` | The folder doesn’t exist. Create it on the server, or fix **Folder on the server**. | | `destination_unreachable` | The host name doesn’t resolve, the port is closed, or the address is private. Temporary errors retry. | See [all error codes](/exports/overview/#error-codes). ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [File formats](/exports/formats/) # Warehouse recipes > Load exported data into Snowflake, Amazon Redshift, Databricks, DuckDB and Looker Studio — minimal, working setups from a cloud storage bucket, BigQuery or Google Sheets. These recipes take data that a [cloud storage bucket](/exports/amazon-s3/), [BigQuery](/exports/bigquery/) or [Google Sheets](/exports/google-sheets/) destination delivers and put it where your team queries it. They are deliberately minimal — a starting point to adapt, not a full pipeline. ## Before you start [Section titled “Before you start”](#before-you-start) * **Use Parquet** for bucket destinations feeding a warehouse. Its columns are typed, so nothing needs parsing, and text arrives exactly as measured. (CSV works, but text that looks like a formula carries a leading `'` — see [CSV](/exports/formats/#csv).) * **Know the layout.** A bucket destination writes `//dt=/.parquet`, and a manifest at `/_manifests/.json` once every file of the run is complete — see [what gets written](/exports/amazon-s3/#what-gets-written). The examples use the folder `isearchfrom` and the `position_tracker.results` dataset, whose folder is `position_tracker-results`. * **Read with your own credentials.** The key you gave iSearchFrom only needs to write. Give your warehouse its own read access to the bucket. * **De-duplicate on `row_id`.** Delivery is at least once: a backfill or a retried run can deliver a row twice, always with the same `row_id`. Every recipe below ends with a de-duplicated view or query — build your reports on that. ## Snowflake [Section titled “Snowflake”](#snowflake) Point an external stage at the folder, then load it with `COPY INTO` — by hand, on a schedule, or continuously with Snowpipe. ```sql CREATE FILE FORMAT isf_parquet TYPE = PARQUET; -- Amazon S3. For Google Cloud Storage use STORAGE_PROVIDER = 'GCS' and gcs:// URLs, -- then grant the service account Snowflake creates read access to the bucket. CREATE STORAGE INTEGRATION isf_bucket TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'S3' ENABLED = TRUE STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789012:role/snowflake-isearchfrom' STORAGE_ALLOWED_LOCATIONS = ('s3://your-bucket/isearchfrom/'); CREATE STAGE isf_stage URL = 's3://your-bucket/isearchfrom/' STORAGE_INTEGRATION = isf_bucket FILE_FORMAT = isf_parquet; -- A table shaped like the files. CREATE TABLE position_tracker_results USING TEMPLATE ( SELECT ARRAY_AGG(OBJECT_CONSTRUCT(*)) FROM TABLE(INFER_SCHEMA( LOCATION => '@isf_stage/position_tracker-results/', FILE_FORMAT => 'isf_parquet', IGNORE_CASE => TRUE )) ); -- Load. COPY INTO remembers the files it has loaded and skips them next time. COPY INTO position_tracker_results FROM @isf_stage/position_tracker-results/ FILE_FORMAT = (FORMAT_NAME = 'isf_parquet') MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE; ``` **Load by manifest.** To load only complete runs, read each new manifest and pass its files to `COPY INTO`. The manifest’s keys include your folder; drop it, since the stage already points there: ```sql COPY INTO position_tracker_results FROM @isf_stage FILES = ('position_tracker-results/dt=2026-09-30/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.parquet') FILE_FORMAT = (FORMAT_NAME = 'isf_parquet') MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE; ``` **Or load continuously with Snowpipe.** Create a pipe over the same `COPY INTO`, then add an S3 event notification for new objects under `isearchfrom/position_tracker-results/` that targets the queue in the pipe’s `notification_channel` (from `SHOW PIPES`). Snowpipe loads every file as it lands, including files of a run that later fails — the de-duplicated view below takes care of those. ```sql CREATE PIPE isf_position_tracker_results AUTO_INGEST = TRUE AS COPY INTO position_tracker_results FROM @isf_stage/position_tracker-results/ FILE_FORMAT = (FORMAT_NAME = 'isf_parquet') MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE; CREATE VIEW position_tracker_results_dedup AS SELECT * FROM position_tracker_results QUALIFY ROW_NUMBER() OVER (PARTITION BY row_id ORDER BY measured_at) = 1; ``` ## Amazon Redshift [Section titled “Amazon Redshift”](#amazon-redshift) Redshift loads Parquet with `COPY … FORMAT PARQUET`. It matches columns **by position**, so the table must have the file’s columns in the file’s order — the [dataset reference](/exports/datasets/) order, or the `columns` list of the manifest. ```sql CREATE TABLE isf_position_tracker_results ( row_id VARCHAR(MAX) NOT NULL, measured_at TIMESTAMP NOT NULL, -- UTC run_id VARCHAR(MAX), campaign_id VARCHAR(MAX), campaign_name VARCHAR(MAX), project_id VARCHAR(MAX), country VARCHAR(MAX), language VARCHAR(MAX), device VARCHAR(MAX), search_engine VARCHAR(MAX), city VARCHAR(MAX), keyword VARCHAR(MAX), domain VARCHAR(MAX), position BIGINT, page BIGINT, url VARCHAR(MAX), title VARCHAR(MAX), description VARCHAR(MAX) ); CREATE TABLE isf_position_tracker_results_staging (LIKE isf_position_tracker_results); -- For each file listed in a new manifest: COPY isf_position_tracker_results_staging FROM 's3://your-bucket/isearchfrom/position_tracker-results/dt=2026-09-30/0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11.parquet' IAM_ROLE 'arn:aws:iam::123456789012:role/redshift-isearchfrom' FORMAT AS PARQUET; -- Then fold the staged rows in, replacing any row already loaded. BEGIN; DELETE FROM isf_position_tracker_results USING isf_position_tracker_results_staging s WHERE isf_position_tracker_results.row_id = s.row_id; INSERT INTO isf_position_tracker_results SELECT * FROM isf_position_tracker_results_staging; TRUNCATE isf_position_tracker_results_staging; COMMIT; ``` * **List columns** (`string_array`, e.g. in `ai_visibility.mentions`) load into a `SUPER` column when you add `SERIALIZETOJSON`: `FORMAT AS PARQUET SERIALIZETOJSON`. JSON columns arrive as text; use `JSON_PARSE` to turn them into `SUPER`. * The bucket must be in the same AWS Region as the cluster. * **When a dataset gains a column**, it is added at the end of the file. Add it to both tables with `ALTER TABLE … ADD COLUMN` before the next load. The same applies if you change the [column pick](/exports/overview/#choosing-columns). * **From CSV** instead: `FORMAT AS CSV IGNOREHEADER 1 GZIP TIMEFORMAT 'auto'`. ## Databricks [Section titled “Databricks”](#databricks) Auto Loader picks up new files in the folder incrementally and remembers what it has loaded. With Unity Catalog, first create an external location for the bucket. ```python ( spark.readStream.format("cloudFiles") .option("cloudFiles.format", "parquet") .option("cloudFiles.schemaLocation", "/Volumes/main/isearchfrom/_meta/position_tracker_results/schema") .option("cloudFiles.partitionColumns", "dt") .load("s3://your-bucket/isearchfrom/position_tracker-results/") .writeStream .option("checkpointLocation", "/Volumes/main/isearchfrom/_meta/position_tracker_results/checkpoint") .trigger(availableNow=True) .toTable("main.isearchfrom.position_tracker_results") ) ``` Run it as a scheduled job after your destination’s schedule. With `availableNow`, each run loads what is new and stops. When a dataset gains a column, Auto Loader stops the stream once with a schema change and picks the column up when the job runs again. ```sql CREATE OR REPLACE VIEW main.isearchfrom.position_tracker_results_dedup AS SELECT * FROM main.isearchfrom.position_tracker_results QUALIFY ROW_NUMBER() OVER (PARTITION BY row_id ORDER BY measured_at) = 1; ``` For Google Cloud Storage, use the `gs://` path. ## DuckDB [Section titled “DuckDB”](#duckdb) DuckDB queries the bucket in place — no loading step. Good for analysis on a laptop and for quick checks of what was delivered. ```sql INSTALL httpfs; LOAD httpfs; CREATE SECRET isf ( TYPE s3, KEY_ID 'AKIA…', SECRET '…', REGION 'eu-central-1' ); SELECT keyword, domain, position, measured_at FROM read_parquet( 's3://your-bucket/isearchfrom/position_tracker-results/*/*.parquet', hive_partitioning = true ) WHERE dt >= '2026-09-01' QUALIFY row_number() OVER (PARTITION BY row_id ORDER BY measured_at) = 1 ORDER BY measured_at DESC; ``` * **Other providers:** for Cloudflare R2 use `TYPE r2` with `ACCOUNT_ID` and `r2://` paths; for Google Cloud Storage `TYPE gcs` with an HMAC key and `gs://` paths; for Wasabi, Backblaze B2, DigitalOcean Spaces and MinIO add `ENDPOINT 's3.eu-central-1.wasabisys.com'` (the host, without `https://`) and, where path-style is used, `URL_STYLE 'path'`. * `dt` comes from the folder names, so filtering on it reads only those days. * CSV files work the same way with `read_csv('…/*/*.csv.gz')`. ## Looker Studio [Section titled “Looker Studio”](#looker-studio) Looker Studio doesn’t read files from a bucket; connect it to a **BigQuery** or **Google Sheets** destination instead. **From BigQuery** — the better choice for anything beyond a small report: 1. In Looker Studio, **Add data → BigQuery**, then choose your project, the export’s dataset and a table such as `isf_position_tracker__checks`. 2. Use `measured_at` as the date range dimension, so report date filters only scan those days of the day-partitioned table. 3. To drop repeated rows, choose **Custom query** instead of a table: ```sql SELECT * FROM `acme-analytics.isearchfrom.isf_position_tracker__checks` WHERE measured_at >= TIMESTAMP(PARSE_DATE('%Y%m%d', @DS_START_DATE)) QUALIFY ROW_NUMBER() OVER (PARTITION BY row_id ORDER BY measured_at) = 1 ``` Tick **Enable date range parameters** so `@DS_START_DATE` follows the report’s date range. **From Google Sheets:** 1. **Add data → Google Sheets**, choose the export’s spreadsheet and a tab such as `position_tracker.checks`, and keep **Use first row as headers** on. 2. Timestamps in Sheets are ISO-8601 text. If Looker Studio reads `measured_at` as text, add a calculated field `PARSE_DATETIME("%Y-%m-%dT%H:%M:%S", LEFT(measured_at, 19))` and use it as the date. ## Related docs [Section titled “Related docs”](#related-docs) * [Cloud storage buckets](/exports/amazon-s3/) * [BigQuery](/exports/bigquery/) * [Google Sheets](/exports/google-sheets/) * [File formats](/exports/formats/) * [Dataset reference](/exports/datasets/) # Webhook destination > Receive exported data as signed JSON batches at your own HTTPS endpoint — payloads, signature verification, idempotency and retries. A webhook destination `POST`s your exported rows to an HTTPS endpoint you run, as signed JSON batches. Use it to feed your own service, a queue, or an automation tool that accepts webhooks. Not the alert webhook This is the **export** webhook. It delivers dataset rows on a schedule. The per-campaign [alert webhook](/reference/webhooks/) is a separate feature with its own payload. ## Set it up [Section titled “Set it up”](#set-it-up) 1. Create a webhook destination with your endpoint URL. It must be a public `https://` URL. 2. Copy the **signing secret** — it starts with `whsec_` and is shown **once**, when the destination is created. Store it in your secret manager. 3. Verify the signature on every request (see below) and answer `2xx`. 4. Use **Test connection** to send an `export.test` event, then run the destination or wait for its schedule. Lost the secret, or it leaked? **Rotate** it: a new `whsec_…` secret is shown once and every request from then on is signed with it. The old one stops being used immediately, so deploy the new secret to your receiver right away. ## What your endpoint receives [Section titled “What your endpoint receives”](#what-your-endpoint-receives) Every request is a `POST` with a JSON body and these headers: | Header | Value | | ----------------- | ----------------------------------------------------------------------------------------- | | `Content-Type` | `application/json` | | `ISF-Signature` | `t=,v1=` — see [Verify the signature](#verify-the-signature) | | `Idempotency-Key` | The event’s `id` — the same on every retry of the same request | A run sends, in order: 1. **`export.batch`** events — the rows, dataset by dataset. 2. One **`export.completed`** event once every dataset has been delivered. ### `export.batch` [Section titled “export.batch”](#exportbatch) ```json { "type": "export.batch", "id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11:position_tracker.results:0", "run_id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11", "destination_id": "0199a2f0-1c4d-7e55-8a21-6b7f0c9d2e33", "dataset": "position_tracker.results", "schema_version": 1, "chunk_index": 0, "rows": [ { "row_id": "0199a7c3-0a8e-7f21-b3c4-5d6e7f809a1b", "measured_at": "2026-09-30T06:02:14.512Z", "run_id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11", "campaign_id": "0199a2f0-77aa-7d10-9e3f-0a1b2c3d4e5f", "campaign_name": "Brand keywords — US", "keyword": "rank tracker", "domain": "example.com", "position": 3, "…": "…" } ] } ``` | Field | Notes | | ---------------- | ------------------------------------------------------------------------------------- | | `type` | `export.batch`. Branch on `type`; ignore types you don’t know. | | `id` | Unique per batch: `::`. Also sent as `Idempotency-Key`. | | `run_id` | The export run the batch belongs to. | | `destination_id` | The destination that sent it. | | `dataset` | Which [dataset](/exports/datasets/) the rows are from. | | `schema_version` | The dataset’s schema version. | | `chunk_index` | 0, 1, 2 … within the dataset, in the order sent. | | `rows` | The rows — objects keyed by the dataset’s column names. | A batch carries **at most 500 rows** and roughly **1 MiB** of rows, whichever comes first. Values keep their JSON types: timestamps are ISO-8601 strings in UTC, lists are arrays, JSON columns are objects, and empty values are `null`. ### `export.completed` [Section titled “export.completed”](#exportcompleted) Sent last, once every dataset has been delivered. Use it to know a run is complete and to check you received every row: ```json { "type": "export.completed", "id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11:completed", "run_id": "0199a7c2-5b1e-7c3a-9f10-3d2b8e6a4c11", "destination_id": "0199a2f0-1c4d-7e55-8a21-6b7f0c9d2e33", "window_from": "2026-09-30T05:00:00.000Z", "window_to": "2026-09-30T05:55:00.000Z", "datasets": [ { "dataset": "position_tracker.results", "rows": 1240 }, { "dataset": "alerts", "rows": 3 } ] } ``` `window_from` and `window_to` are the time range the run covered. ### `export.test` [Section titled “export.test”](#exporttest) What **Test connection** sends: ```json { "type": "export.test", "id": "test:1759212000000" } ``` Answer `2xx`. A test sent **before** the destination is saved is signed with a throwaway secret — you don’t have the real one yet — so it will not pass verification. Accept `export.test` without processing it, or save first and test the saved destination, which is signed with its real secret. ## Verify the signature [Section titled “Verify the signature”](#verify-the-signature) `ISF-Signature` is `t=,v1=`, where the signature is the hex-encoded HMAC-SHA256 of `"."`, keyed with your whole signing secret (including the `whsec_` prefix). To verify: 1. Split the header on `,` and read `t` and `v1`. 2. Reject the request if `t` is more than **5 minutes** from your clock — this stops replays. 3. Compute the HMAC over the **raw body bytes** exactly as received. Parsing and re-serializing the JSON first changes the bytes and breaks the signature. 4. Compare with `v1` in **constant time**. ### Node.js [Section titled “Node.js”](#nodejs) ```js import { createHmac, timingSafeEqual } from 'node:crypto'; const TOLERANCE_SECONDS = 5 * 60; export function verifyIsfSignature(rawBody, header, secret) { const parts = Object.fromEntries( (header ?? '').split(',').map((part) => part.trim().split('=', 2)) ); const timestamp = Number(parts.t); if (!Number.isInteger(timestamp) || !parts.v1) return false; if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false; const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest(); const received = Buffer.from(parts.v1, 'hex'); return received.length === expected.length && timingSafeEqual(received, expected); } // Express: keep the body raw on this route. // app.post('/isf-export', express.raw({ type: 'application/json' }), (req, res) => { // if (!verifyIsfSignature(req.body, req.get('ISF-Signature'), process.env.ISF_WEBHOOK_SECRET)) { // return res.sendStatus(401); // } // const event = JSON.parse(req.body); // // … store it, then: // res.sendStatus(204); // }); ``` ### Python [Section titled “Python”](#python) ```python import hashlib import hmac import time TOLERANCE_SECONDS = 5 * 60 def verify_isf_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict( part.strip().split("=", 1) for part in (header or "").split(",") if "=" in part ) try: timestamp = int(parts["t"]) received = parts["v1"] except (KeyError, ValueError): return False if abs(time.time() - timestamp) > TOLERANCE_SECONDS: return False signed = f"{timestamp}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, received) # Flask: # @app.post("/isf-export") # def isf_export(): # if not verify_isf_signature(request.get_data(), request.headers.get("ISF-Signature"), SECRET): # abort(401) # event = request.get_json() # ... # return "", 204 ``` ## Delivery, retries and duplicates [Section titled “Delivery, retries and duplicates”](#delivery-retries-and-duplicates) * **Answer `2xx`** once you have stored the event. Anything else is a failure. If processing is slow, store the body and process it asynchronously — each request times out after **15 seconds**. * **Retried:** a timeout, a connection error, `408`, `429` or any `5xx`. The run is retried automatically; batches already delivered for finished datasets are not sent again, and a re-sent batch has the same `id` and `Idempotency-Key`. * **Not retried:** any other `4xx`, or a redirect — redirects are never followed. The run fails and the destination is marked **needs attention** (`401`/`403` report `access_denied`, `404`/`410` report `not_found`, anything else `destination_rejected`). Fix your endpoint, then resume the destination. * **Batches arrive in order**, one at a time, never in parallel. Two levels of de-duplication cover every case: 1. **Per request** — skip an `id` (or `Idempotency-Key`) you have already processed. This absorbs retries. 2. **Per row** — upsert on `(dataset, row_id)`. A run that failed half-way is covered again by the next one under a new `run_id`, and a backfill re-sends older rows; both repeat rows with the same `row_id`. ## Security [Section titled “Security”](#security) * Always verify `ISF-Signature` and the timestamp before trusting a body. * Endpoints on private or internal networks are refused; the URL must resolve to a public address. * Rotate the signing secret when someone who had it leaves, or if it may have leaked. ## Related docs [Section titled “Related docs”](#related-docs) * [Exporting your data](/exports/overview/) * [Dataset reference](/exports/datasets/) * [Alert webhooks](/reference/webhooks/) # Choose the Right Tool > Decide which iSearchFrom feature to use for each customer workflow. Start with the outcome you want, then choose the lightest tool that answers the question. You can always move from one-time research into recurring tracking after the settings are proven. ## Tool comparison [Section titled “Tool comparison”](#tool-comparison) Direct Search Best for fast manual checks. Use it when you want to open Google or Bing with specific country, language, city, domain, and SafeSearch settings. Search Engine Proxy Best for captured SERP research, parsed result items, HTML preview, map pin targeting, compare mode, and deeper result pages. SERP Position Tracker Best for recurring organic keyword and domain ranking checks over time. Ads Position Tracker Best for recurring checks of sponsored placements, competitor ads, and brand terms. Maps Visibility Where a business ranks on the Google Maps canvas, from many points around it — a map of ranks rather than one rank. For local SEO and service-area coverage. Marketplace Visibility Where your products rank on Amazon, Google Shopping and Walmart, at what price, and how much of the shelf competing and sponsored listings hold. AI Data Chat Ask the data your campaigns already collected a question in plain language. Answers arrive with the SQL and the rows behind them. AI Visibility Best for understanding how AI platforms mention, rank, recommend, or omit your brand. MCP Server Best when you want Claude, ChatGPT, Cursor or another AI agent to run searches and read your tracking data for you, with no code. Free on every plan. API Best when you want to create searches, list results, or integrate iSearchFrom data into your own workflow. ## Common decisions [Section titled “Common decisions”](#common-decisions) * Direct vs proxy Use [Direct Search](/search-tools/direct-search/) when speed matters and you are comfortable viewing results in the search engine. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) when you need parsed results, HTML preview, saved evidence, map pin targeting, extra result pages, comparison between settings, or team history inside the organization. * Proxy vs tracker Use Search Engine Proxy for one-time research. Use a tracker when the same question needs to be answered repeatedly. Examples: * “What does Google show in Berlin today?” -> Search Engine Proxy * “Did our domain move up or down this month?” -> SERP Position Tracker * “Did competitors start bidding on our brand term?” -> Ads Position Tracker * Organic vs ads Use [SERP Position Tracker](/tracking/serp-position-tracker/) for organic rankings. Use [Ads Position Tracker](/tracking/ads-position-tracker/) for sponsored search ads. If a placement is not marked as sponsored, it belongs in SERP Position Tracker. If the placement is a paid ad, use Ads Position Tracker. * Search vs AI Use search tracking when you care about search engine result pages. Use [AI Visibility](/ai-visibility/overview/) when you care about generated AI responses. AI Visibility is not a replacement for rank tracking. It answers whether your brand is mentioned, recommended, compared, or omitted in AI answers. ## Recommended starting point by role [Section titled “Recommended starting point by role”](#recommended-starting-point-by-role) [SEO manager](/search-tools/search-engine-proxy/)Start with Search Engine Proxy, then move proven keyword sets into SERP Position Tracker. [PPC manager](/tracking/ads-position-tracker/)Start with Search Engine Proxy for evidence, then monitor recurring paid visibility. [Agency strategist](/search-tools/projects/)Use projects first, then add proxy searches, trackers, exports, and client reporting. [Founder or operator](/guides/quickstart/)Start with Direct Search and AI Visibility to answer market and brand questions quickly. [Developer or data team](/reference/api/)Use API keys and customer-facing API endpoints for automation and reporting. If you are unsure Start with [Search Engine Proxy](/search-tools/search-engine-proxy/). It gives you a concrete result page, uses the same settings as tracking tools, and helps confirm market, language, device, and domain settings before scheduling campaigns. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Use the left sidebar to choose Direct Search, Search Engine Proxy, a tracker, or AI Visibility after selecting the active organization. # Core Concepts > Learn the product vocabulary used across iSearchFrom. These concepts appear throughout iSearchFrom. Understanding them makes every guide easier to follow. ## Organization [Section titled “Organization”](#organization) A organization is the team or business account where work happens. It contains: * projects * searches * tracker campaigns * AI Visibility campaigns * members and roles * billing and token balance * API keys If you belong to multiple organizations, choose the active organization before running searches or creating campaigns. ## Project [Section titled “Project”](#project) A project is a folder for related work. Use projects to organize by client, website, market, product line, or campaign. Examples: * `Acme - Germany SEO` * `Brand PPC Watchlist` * `AI Visibility - Launch` * `Marketplace Search Research` Projects are especially important for teams because saved searches and campaigns become easier to filter and review. ## Search [Section titled “Search”](#search) A search is a one-time action. Direct Search creates a destination URL and sends you to Google or Bing. Search Engine Proxy runs a captured search and stores the results inside iSearchFrom. ## Campaign [Section titled “Campaign”](#campaign) A campaign is a saved tracking setup. Campaigns are used by: * SERP Position Tracker * Ads Position Tracker * AI Visibility Campaigns include settings such as project, target domains or brand names, keywords or prompts, locations, devices, alerts, and schedule. ## Run [Section titled “Run”](#run) A run is one execution of a campaign. Runs can be scheduled or manual. For example, if a SERP Position Tracker campaign runs every Monday, each Monday execution is a run. ## Check [Section titled “Check”](#check) A check is one unit of work inside a run. Examples: * one keyword in one market for SERP Position Tracker * one keyword in one market for Ads Position Tracker * one prompt on one AI platform in one location for AI Visibility More checks usually means more complete coverage, but it can also increase token usage. ## Search settings [Section titled “Search settings”](#search-settings) Search settings control what market and result shape you are testing: * **Query**: the search term or phrase. * **Country**: the market where the search is performed. * **City**: a city-level location when available. * **Map pin**: an exact latitude/longitude origin for Search Engine Proxy. * **Language**: the search/interface language. * **Device**: desktop, mobile, or tablet for proxy and tracker workflows. * **Engine**: Google or Bing where supported. * **Domain**: the search engine domain, such as a country-specific Google domain. * **SafeSearch**: search engine filtering behavior. * **Country only**: restrict results to pages from the selected country when supported. * **Language only**: restrict results to pages in the selected language when supported. ## Tokens [Section titled “Tokens”](#tokens) Tokens are usage credits for work that performs captured searches or automated checks. More pages, keywords, prompts, markets, platforms, and frequent schedules can increase usage. See [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) before creating large campaigns. ## Alerts [Section titled “Alerts”](#alerts) Alerts notify you when important changes are detected, such as: * a tracked domain entering or leaving the top 10 * a meaningful ranking movement * a tracked advertiser starting or stopping sponsored ads * AI visibility appearing, disappearing, rising, or falling * competitor activity in AI answers Alerts are configured inside tracker or AI Visibility campaign setup. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. The organization selector, product sidebar, and token balance live in the app shell. Switch organizations before running searches or changing billing. # Welcome to iSearchFrom > What iSearchFrom helps customers do and how the product is organized. iSearchFrom is a search intelligence platform for teams that need to understand what customers see in different markets: localized search results, paid placements, organic rank movement, and brand visibility in AI answers. ## What you can answer [Section titled “What you can answer”](#what-you-can-answer) What does Google show in this market?Run a localized search by country, city, language, device, search engine domain, and SafeSearch setting. Where does my domain rank?Track organic positions for your domain and competitors across selected keywords and markets. Are competitors buying ads?Monitor sponsored placements, headlines, display URLs, and landing URLs for the queries that matter. How do AI platforms mention us?Measure whether AI answer surfaces mention, recommend, rank, or omit your brand and competitors. ## Start by job [Section titled “Start by job”](#start-by-job) [Run a quick manual checkUse Direct Search when speed matters more than saved evidence.](/search-tools/direct-search/)[Capture a result pageUse Search Engine Proxy for parsed results, HTML preview, and repeatable research.](/search-tools/search-engine-proxy/)[Track organic visibilityUse SERP Position Tracker for recurring keyword and domain ranking checks.](/tracking/serp-position-tracker/)[Monitor AI answersUse AI Visibility when customers ask AI systems for recommendations.](/ai-visibility/overview/) ## How the product is organized [Section titled “How the product is organized”](#how-the-product-is-organized) Every workspace shares the same vocabulary. These terms appear throughout the app and this documentation. | Concept | What it is | | ---------------- | ------------------------------------------------------------------------------- | | **Organization** | Holds team members, projects, billing, token balance, API keys, and saved data. | | **Project** | Groups searches and campaigns by client, market, product line, or experiment. | | **Search** | A one-time Direct Search or Search Engine Proxy run. | | **Campaign** | A recurring tracker or AI Visibility setup. | | **Run** | One execution of a campaign. | | **Check** | One keyword, prompt, location, or platform combination inside a run. | | **Tokens** | Usage credits for tools that perform captured searches or recurring checks. | Best first path Start with [Quick Start](/guides/quickstart/) if you want a guided workflow. Use [Choose the Right Tool](/guides/choose-tool/) if you already know the business question but are unsure which feature fits. ## Next steps [Section titled “Next steps”](#next-steps) [Quick StartCreate the first useful workflow: organization, project, search, save, and next action.](/guides/quickstart/)[Choose the Right ToolCompare Direct Search, Search Engine Proxy, trackers, AI Visibility, and the API.](/guides/choose-tool/)[Plans, Tokens, and LimitsUnderstand feature access and usage before expanding campaigns.](/guides/plans-tokens-limits/) ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Start from the app home, choose the active organization, then use the sidebar to open the tool that matches your job. # Plans, Tokens, and Limits > Understand plans, token usage, feature access, and practical limits. Plans control feature access, organization size, and included token allocation. Tokens are usage credits for captured searches, recurring tracker runs, and AI Visibility checks. ## Token basics [Section titled “Token basics”](#token-basics) Lightest workflow Direct Search builds a search URL and sends you to the search engine. It does not capture a full result page inside iSearchFrom. Token-using work Search Engine Proxy searches, additional proxy pages, tracker runs, ad tracker runs, AI Visibility checks, and manual campaign runs can spend tokens. Billing cycle Subscription tokens reset to the plan allocation on renewal. Extra top-up tokens are permanent organization tokens and do not reset at renewal. Review usage Use [Billing](https://app.isearchfrom.com/billing) to review subscription tokens remaining, extra tokens, recent usage, and next billing date. Subscription tokens are spent before top-up tokens. If the subscription balance reaches zero, token-using work can continue only when the organization still has permanent top-up tokens. ## What increases usage [Section titled “What increases usage”](#what-increases-usage) * Searches More proxy result pages increase usage because each additional page requires more captured search work. Start with one page unless deeper coverage is required. * Trackers More keywords and more frequent schedules increase recurring campaign work. Tracked domains affect what the campaign reports; keyword checks and run frequency drive token usage. Daily campaigns run more often than weekly or monthly campaigns. * AI Visibility More prompts, locations, and platforms expand the check matrix: ```text prompts x locations x platforms = checks per run ``` * Manual runs Running a campaign now can spend tokens outside its normal schedule. Review token usage after manual runs while testing campaign setup. ## Feature access by plan [Section titled “Feature access by plan”](#feature-access-by-plan) Plans can control access to projects, organization members, Search Engine Proxy usage, Direct Search usage, API access, AI Visibility, email digests, exports, priority support, SSO, the [Audit Log](/team-account/audit-log/) on Agency, and how many months of campaign history are kept (12, 24, 36 or 60 by plan; see [Data Retention](/team-account/data-retention/)). Upgrade messages If a page shows an upgrade message, the active organization does not currently have the required feature. Confirm the active organization before changing billing settings. ## Practical product limits [Section titled “Practical product limits”](#practical-product-limits) SERP Position Tracker Requires at least 1 tracked domain and 1 keyword. Supports up to 10 tracked domains and 10 keywords per campaign. Ads Position Tracker Requires at least 1 tracked domain and 1 keyword. Supports up to 10 tracked domains and 10 keywords per campaign. AI Visibility Requires at least 1 brand name, 1 prompt, 1 location, and 1 platform. Supports up to 20 brand names, 20 competitors, 50 prompts, 50 locations, and 500 checks per run. Search Engine Proxy Supports up to 10 result pages per search request. Load additional pages only when the first page does not answer the question. These limits keep campaigns useful and predictable. If you need larger monitoring, split work into focused campaigns by market, product, or client. ## Recommended usage patterns [Section titled “Recommended usage patterns”](#recommended-usage-patterns) ### Start narrow [Section titled “Start narrow”](#start-narrow) Create one campaign with a small keyword or prompt set. Use one or two priority locations before adding every market. ### Verify settings [Section titled “Verify settings”](#verify-settings) Run the workflow manually or wait for the first scheduled run. Confirm country, city, language, domain, device, and platform settings before expanding. ### Review the signal [Section titled “Review the signal”](#review-the-signal) Check whether the result answers a decision or report question. If it does, expand keywords, prompts, competitors, pages, or locations gradually. ### Pause stale work [Section titled “Pause stale work”](#pause-stale-work) Pause campaigns that no longer feed a decision. Remove test campaigns after setup so token usage stays easy to explain. ## Billing safety tips [Section titled “Billing safety tips”](#billing-safety-tips) Schedules Use weekly schedules before switching a new campaign to daily. Campaign size Avoid one oversized AI Visibility campaign when several focused campaigns are easier to understand. Names and notes Use project names and campaign descriptions so teammates understand why usage exists. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app. Billing shows your current plan, included subscription tokens, extra top-up tokens, next billing date, and recent token usage. ## Related docs [Section titled “Related docs”](#related-docs) [Manage Subscription](/billing/manage-subscription/)Review plan status and billing settings. [Token Usage](/billing/token-usage/)Understand where tokens are used and how to review organization usage. [Token Top-ups](/billing/token-top-ups/)Add extra usage capacity outside the plan allocation. # Quick Start > Run your first customer-facing workflow in iSearchFrom. This quick start walks through the first useful workflow for most customers: create a organization context, run a localized search, save it, and choose the next workflow. Use one real scenario Pick one market, one search query, and one project before exploring every setting. The first workflow is easier to judge when it answers a real customer or campaign question. ## First workflow [Section titled “First workflow”](#first-workflow) ### Sign in [Section titled “Sign in”](#sign-in) Open the [iSearchFrom app](https://app.isearchfrom.com/) and sign in or create an account. If you were invited to a team, accept the invitation from your email or from the invitations screen. If your company uses SSO, use the sign-in method your organization owner configured. You are ready to continue when the app shows the organization switcher and the main product navigation. ### Choose a organization [Section titled “Choose a organization”](#choose-a-organization) Your organization controls projects, team access, billing, token balance, API keys, and saved searches. If you belong to more than one organization, choose the active organization from the app shell before running searches. ### Create a project [Section titled “Create a project”](#create-a-project) Open **Projects** and create a project for the work you are about to do. Projects keep searches, trackers, campaigns, and reports grouped by client, market, or initiative. Good names are specific: * `UK Local SEO Audit` * `Q3 Brand PPC Monitoring` * `Brazil Marketplace Research` * `AI Visibility - Product Launch` ### Run your first search [Section titled “Run your first search”](#run-your-first-search) In the app, open **Search Tools -> Direct Search**, enter a query, choose Google or Bing, select country and language, then optionally choose a city, search domain, and SafeSearch setting. Direct Search builds the search URL and opens the search engine directly. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) instead when you need captured results, parsed SERP items, HTML preview, comparison, or additional result pages. ### Save useful searches [Section titled “Save useful searches”](#save-useful-searches) Save a search if you expect to repeat it. Use a name that includes the market and intent, for example `US - brand alternatives - desktop`. Saved searches are useful for monthly checks, client research, teammate handoff, and validating settings before creating a tracker. ### Watch token usage [Section titled “Watch token usage”](#watch-token-usage) Tools that capture results or run recurring checks use tokens. Review [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) before increasing pages, keywords, prompts, markets, platforms, or schedule frequency. ## Pick your next workflow [Section titled “Pick your next workflow”](#pick-your-next-workflow) * SEO [Captured SERP research](/search-tools/search-engine-proxy/)Validate location, language, device, and SERP layout before creating recurring monitoring. [Organic rank tracking](/tracking/serp-position-tracker/)Monitor domain movement for keywords that should appear in recurring reports. * PPC [Check current ads](/search-tools/search-engine-proxy/)Capture the current paid result page before deciding what to monitor. [Monitor paid placements](/tracking/ads-position-tracker/)Track competitor ads, brand terms, and landing-page behavior over time. * AI [AI Visibility overview](/ai-visibility/overview/)Understand what AI Visibility tracks and when to use it. [Create a campaign](/ai-visibility/create-campaign/)Set up prompts, brands, competitors, locations, platforms, and schedules. ## Quick choices [Section titled “Quick choices”](#quick-choices) Fast manual SERP check Start with [Direct Search](/search-tools/direct-search/). Captured localized SERP results Start with [Search Engine Proxy](/search-tools/search-engine-proxy/). Ongoing organic rank tracking Start with [SERP Position Tracker](/tracking/serp-position-tracker/). Ongoing PPC ad monitoring Start with [Ads Position Tracker](/tracking/ads-position-tracker/). Brand visibility in AI answers Start with [AI Visibility](/ai-visibility/overview/). Automating with your own system Start with [API Overview](/reference/api/). ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Start in the app, choose the active organization, then follow the workflow links in this guide. # Contact Support > Get help with account, billing, API, search, tracker, and AI Visibility questions. Contact support when documentation and troubleshooting do not resolve the issue. ## Before contacting support [Section titled “Before contacting support”](#before-contacting-support) Gather: * organization name * project name * search or campaign name * date and time of the issue * browser and device if UI-related * endpoint and request ID if API-related * screenshots when helpful * exact error message Do not send API keys, passwords, payment card details, or other secrets. ## Good support requests [Section titled “Good support requests”](#good-support-requests) Good: ```text Organization: Acme SEO Project: UK Local SEO Campaign: London Plumbing Rankings Issue: Manual run failed on June 16, 2026 around 14:30 UTC. Error shown: Failed to load campaign results. Expected: latest run should show keyword results for 5 keywords. ``` Less useful: ```text It does not work. ``` ## Billing and account issues [Section titled “Billing and account issues”](#billing-and-account-issues) For billing issues, include the organization and invoice or payment date if visible. Do not include card numbers. For access issues, include the email address on your iSearchFrom account and the organization you expected to access. ## API issues [Section titled “API issues”](#api-issues) For API issues, include: * endpoint * HTTP method * response status * response body * sanitized request body * key permission scope, without sharing the key itself ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Account Settings](https://app.isearchfrom.com/settings/account) in the app. When reporting an issue, include the active organization and links or IDs from the relevant app page, but never include passwords, API keys, or card details. ## Related docs [Section titled “Related docs”](#related-docs) * [Troubleshooting](/help/troubleshooting/) * [Authentication and API Keys](/reference/authentication-api-keys/) * [Invoices and Payments](/billing/invoices-payments/) # FAQ > Frequently asked questions about iSearchFrom. ## Is Direct Search the same as Search Engine Proxy? [Section titled “Is Direct Search the same as Search Engine Proxy?”](#is-direct-search-the-same-as-search-engine-proxy) No. Direct Search builds a URL and opens the search engine. Search Engine Proxy captures results inside iSearchFrom and can provide parsed results, HTML preview, saved pages, extra pages, and comparison. ## Why do my results differ from my own browser? [Section titled “Why do my results differ from my own browser?”](#why-do-my-results-differ-from-my-own-browser) Search results can vary because of location, language, device, account personalization, search domain, SafeSearch, time, and search engine experiments. ## Which tool should I start with? [Section titled “Which tool should I start with?”](#which-tool-should-i-start-with) Start with Search Engine Proxy if you are unsure. It lets you verify settings and inspect captured results before creating recurring campaigns. ## Do trackers replace manual searches? [Section titled “Do trackers replace manual searches?”](#do-trackers-replace-manual-searches) No. Trackers are for recurring monitoring. Manual proxy searches are still useful for investigation, QA, and one-off research. ## How are AI Visibility checks counted? [Section titled “How are AI Visibility checks counted?”](#how-are-ai-visibility-checks-counted) AI Visibility checks are based on prompt, location, and platform combinations. For example, 10 prompts across 2 locations and 3 platforms equals 60 checks per run. ## Can I use the API for everything in the app? [Section titled “Can I use the API for everything in the app?”](#can-i-use-the-api-for-everything-in-the-app) The API supports selected customer workflows. Some app actions may be session-only or not exposed as public customer API endpoints. ## Why is a feature asking me to upgrade? [Section titled “Why is a feature asking me to upgrade?”](#why-is-a-feature-asking-me-to-upgrade) The active organization plan may not include that feature, or the organization may have reached a plan limit. ## Where do I manage invoices? [Section titled “Where do I manage invoices?”](#where-do-i-manage-invoices) Open [Billing](https://app.isearchfrom.com/billing) for payment history and invoice links when available. Payment details are handled by Stripe. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Most answers depend on the active organization, plan, and tool. Check the app first, then use the linked docs for the exact workflow. ## Related docs [Section titled “Related docs”](#related-docs) * [Choose the Right Tool](/guides/choose-tool/) * [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) * [Troubleshooting](/help/troubleshooting/) # Troubleshooting > Fix common issues with searches, campaigns, billing, tokens, teams, and API access. Use this page when something does not work as expected. Check organization first Many issues come from the wrong active organization, a pending invitation, or a role that does not have access to the current page. ## Common issues [Section titled “Common issues”](#common-issues) * Organization Check that you are signed in, the correct organization is active, you accepted the organization invitation, and your role has access to the page. * Creation If you cannot create a project or campaign, check that a organization is active, your role allows creation, your plan includes the feature, you have not reached limits, and required fields are complete. * Billing If billing or tokens look wrong, check that the correct organization is active, subscription status is active or trialing, payment is not failed or past due, token top-up payment completed, and usage is not from a scheduled or manual campaign run. ## Search and campaign fixes [Section titled “Search and campaign fixes”](#search-and-campaign-fixes) A proxy search failed Rerun the search, reduce pages to one, confirm country/language/city settings, remove map pin and try country/city targeting, then try again in a few minutes. A tracker campaign has no results Check that the campaign is active, the schedule has run, keywords are valid, tracked domains omit protocol and paths, settings are correct, and run status is not failed or pending. AI Visibility looks incomplete Check that prompts are specific, locations and platforms are compatible, the run completed, raw responses have context, and competitor or brand names are spelled consistently. API key does not work Check that the key is active, sent in `X-API-Key`, has required permissions, the organization plan includes API access, and the endpoint is customer-facing. ## Quick diagnostic path [Section titled “Quick diagnostic path”](#quick-diagnostic-path) 1. Confirm the active organization. 2. Confirm your role and plan access. 3. Check required fields and limits. 4. Check run or request status. 5. Retry with the smallest valid scope. 6. Contact support with the project, campaign, search, or API request details. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Start troubleshooting from the page where the issue happened, then check Billing, Team Settings, or API Keys when the issue involves access or tokens. ## Related docs [Section titled “Related docs”](#related-docs) [FAQ](/help/faq/)Answers to common product questions. [Contact Support](/help/contact-support/)What to include when contacting support. [Errors, Pagination, and Rate Limits](/reference/errors-pagination-rate-limits/)Debug API errors and request limits. # Import from Google Ads > Connect your Google Ads account and let iSearchFrom build position and ad tracking from your live campaigns — no CSV, no Ads Editor, no rebuilding what you already set up. Connect Google Ads once, choose which campaigns matter, and iSearchFrom creates a project, a keyword position tracker and an ad tracker from what is already running — pointed at the same places, languages and devices your campaigns target. ## Why this exists [Section titled “Why this exists”](#why-this-exists) Google Ads reports **delivery**: how often your ad was eligible, how many impressions it won, what you paid. It cannot tell you what the result page actually looked like to a person searching from Vilnius on a phone. That is the gap iSearchFrom fills, and the import exists so you do not have to re-type your campaign into a second tool to get there. ## What you need [Section titled “What you need”](#what-you-need) * A Google account with access to the Google Ads account you want to import. * The **Google Integrations** feature on your plan. * Nothing installed. No Ads Editor, no CSV export, no spreadsheet cleanup. ## Connect [Section titled “Connect”](#connect) 1. **Open Settings → Integrations.** 2. **Press Connect on the Google Ads card.** Google handles the login and consent; iSearchFrom never sees your password. 3. **Grant access.** The consent screen mentions managing campaigns — see [What we can and cannot do](#what-we-can-and-cannot-do) below for why, and what we actually do. 4. **You land back on the integrations page** with the account connected. ## Import a campaign [Section titled “Import a campaign”](#import-a-campaign) 1. **Press Import campaigns.** 2. **Choose the advertiser account.** Accounts under a Manager (MCC) account are included — if you manage clients, you will see theirs, not just the manager itself. If you only have one account, this step is skipped. 3. **Choose campaigns.** Enabled Search campaigns start ticked. Paused ones are shown but unticked. Campaign types we cannot track yet (Performance Max, Shopping, Display, Demand Gen, YouTube) are listed separately so you can see they were considered rather than lost. 4. **Review.** This screen shows exactly what will be created and what it will cost per month, before anything exists. 5. **Create.** The project and trackers are created and the first check starts immediately. ## What gets imported [Section titled “What gets imported”](#what-gets-imported) | From Google Ads | Becomes | How | | -------------------------- | -------------------------- | ----------------------------------------------------------------------- | | Account and campaign name | Project and campaign names | Readable names; Google’s IDs are kept internally | | Final URLs | Tracked domains | Hostnames only, `www.` and tracking parameters stripped | | Enabled keywords | Candidate queries | Deduplicated across case and accents; broad match flagged | | Recent search terms | Recommended queries | Terms that earned clicks in the last 30 days, most clicked first | | Location targets | Country / city | Resolved through Google’s own geo data | | Proximity (radius) targets | Coordinates | Used directly — the most precise pin available | | Languages | Search language | Mapped to the tracker’s language | | Device bid modifiers | Tracked device | Whichever device the campaign bids up | | Responsive search ads | Creative identity | Headlines and descriptions, so your own ads are recognisable in results | ### Why it does not import every keyword [Section titled “Why it does not import every keyword”](#why-it-does-not-import-every-keyword) A broad keyword like `dog grooming` stands in for hundreds of different searches. Tracking the keyword measures a query nobody typed. So the default is a mix: **your enabled keywords, plus the search terms that actually earned clicks** — ranked with real searches first, deduplicated, and capped. Every choice is explained on the review screen under **Worth knowing**, including anything that was skipped and why. Search terms are always partial Google withholds search terms below a privacy threshold, and withholds proportionally more from smaller accounts. The list you see is real but never complete. ### Locations cost extra, so you pick them [Section titled “Locations cost extra, so you pick them”](#locations-cost-extra-so-you-pick-them) A tracker campaign covers **one location**. Tracking three cities means three campaigns per tool, and three times the checks. The import therefore defaults to **one location** — the most precise one it found — and lists the rest on the review screen. Adding one updates the monthly cost immediately, before you commit. One import builds at most **40 campaigns** across both tools, from at most 50 source campaigns and 20 locations. Anything past that is listed on the review screen rather than built — import fewer campaigns or locations at a time to include it. ## No Google connection yet? Import an export [Section titled “No Google connection yet? Import an export”](#no-google-connection-yet-import-an-export) If your Ads account is not connected — the consent is still pending, or you simply want to try it — iSearchFrom can read a **Google Ads Editor export** instead. Everything after that point is identical: the same preview, the same cost estimate, the same trackers. 1. **In Google Ads Editor, export your account** (or just the campaigns you care about). Either CSV or the default tab-separated export works. 2. **Send it to the import endpoint.** There is no upload screen yet, so this step is currently an API call — ask your developer, or wait for the button. 3. **Review and create**, exactly as above. An export has no search terms An Ads Editor export holds your account’s **configuration** — campaigns, keywords, ads, targeting. Search terms are reporting data and are not in it. So a CSV import plans from your keywords alone, and the preview says so. The connected path is better precisely because it can see what people actually typed. ## What it costs [Section titled “What it costs”](#what-it-costs) The review screen shows the monthly token cost, calculated with the same arithmetic that charges your account. Trackers created by an import run **daily**, and you can change that on the campaign afterwards like any other. If a first check does not start, it is almost always a token balance — the campaigns still exist and will run on their next scheduled round. If the build itself stops part-way — a campaign name already taken, a plan limit reached — the import shows why and keeps everything it created. **Apply it again** to finish the rest; nothing already created is made twice. A campaign you deleted between the review and the build is skipped and named in the result rather than rebuilt. ## What we can and cannot do [Section titled “What we can and cannot do”](#what-we-can-and-cannot-do) iSearchFrom only ever **reads** your campaign configuration and reporting. It never creates, edits, pauses or deletes anything in Google Ads, and never touches budgets or bids. Google’s consent screen will nonetheless mention managing campaigns. That is not us asking for more than we need — **Google Ads has no read-only permission.** A single scope covers both reading and writing, so any tool that reads your campaigns is granted the same scope one that changes them would use. What we do about that: * The code that talks to Google Ads has **no way to express a write**. There is one request path and it can only query. This is enforced by a test, not by a policy. * Disconnecting revokes our access at Google immediately, from **Settings → Integrations**. * Your credentials are encrypted at rest with a key held separately from everything else. ## Keeping it connected [Section titled “Keeping it connected”](#keeping-it-connected) Connections occasionally expire — you changed your Google password, revoked access, or the grant aged out. The integrations page will say so and offer **Reconnect**. Existing trackers keep running; only importing again needs the connection. ## Who can do what [Section titled “Who can do what”](#who-can-do-what) | | Owner | Admin | Member | | ------------------------ | :---: | :---: | :----: | | Connect a Google account | ✓ | ✓ | — | | Disconnect | ✓ | ✓ | — | | Import campaigns | ✓ | ✓ | ✓ | | See what is connected | ✓ | ✓ | ✓ | A member can import from a connection an administrator set up, but cannot attach or remove the credential itself — because that credential can reach your ad spend. Organizations that want a different split can create a custom role with the **Integration** permission. Search Console Connect Search Console too, and compare what Google says you rank for with what we measure from a real location and device. Not supported yet Performance Max, Shopping, Display, Demand Gen and YouTube campaigns are listed during import but not turned into trackers. # Connect OpenClaw > Give a self-hosted OpenClaw AI agent access to iSearchFrom localized search, SERP, and ad data — over the hosted iSearchFrom MCP server, or a local bridge for stdio-only setups. [OpenClaw](https://github.com/openclaw/openclaw) is an open-source, self-hosted AI agent with native Model Context Protocol (MCP) support. Because iSearchFrom runs a **hosted MCP server**, the simplest path is to point OpenClaw straight at it — no bridge, no API key in a config file. A local bridge is still available if you specifically want stdio transport or a hand-picked tool subset. New: connect to the hosted server directly Earlier versions of this guide built a local REST-to-MCP bridge because there was no hosted endpoint. That’s no longer necessary — iSearchFrom now runs a first-party MCP server at `https://api.isearchfrom.com/mcp`. OpenClaw supports HTTP/SSE MCP transports, so it can connect natively. The bridge below is now an **optional** fallback, not the default. ## Option A — Connect to the hosted MCP server (recommended) [Section titled “Option A — Connect to the hosted MCP server (recommended)”](#option-a--connect-to-the-hosted-mcp-server-recommended) This is the least setup and gives OpenClaw the full, permission-scoped tool catalog with OAuth — your credentials never sit in a config file. ### Add the server to OpenClaw [Section titled “Add the server to OpenClaw”](#add-the-server-to-openclaw) Add iSearchFrom with the OpenClaw CLI. OpenClaw defaults remote servers to SSE, so set the streamable HTTP transport and OAuth explicitly: ```bash openclaw mcp add isearchfrom --url https://api.isearchfrom.com/mcp --transport streamable-http --auth oauth ``` Or add the same entry to `~/.openclaw/openclaw.json` yourself: ```json { "mcp": { "servers": { "isearchfrom": { "url": "https://api.isearchfrom.com/mcp", "transport": "streamable-http", "auth": "oauth" } } } } ``` ### Authorize [Section titled “Authorize”](#authorize) Run `openclaw mcp login isearchfrom`. OpenClaw opens a browser to sign in to iSearchFrom, pick the organization the agent acts in, and approve permissions. If the browser can’t reach OpenClaw’s loopback address, finish with `openclaw mcp login isearchfrom --code `. See [Permissions & security](/mcp/security/) to scope this tightly. ### Verify [Section titled “Verify”](#verify) ```bash openclaw mcp list openclaw mcp doctor isearchfrom --probe ``` You should see `isearchfrom` connected with its tools available. That’s it — your agent now has the tools in the [tool catalog](/mcp/tools/), bounded by the permissions you approved. The rest of this page is only needed if you want the local-bridge approach. ## Option B — Local MCP bridge (stdio, hand-picked tools) [Section titled “Option B — Local MCP bridge (stdio, hand-picked tools)”](#option-b--local-mcp-bridge-stdio-hand-picked-tools) Use this only if you need **stdio** transport, want to expose a deliberately small tool subset, or run in an environment where the OAuth browser flow isn’t practical. The bridge is a small local server you own that turns iSearchFrom REST calls into MCP tools, authenticated with an [API key](/reference/authentication-api-keys/). The agent can spend tokens A bridge that exposes `run_proxy_search` lets an autonomous agent run real, billable searches in a loop. Use a search-scoped key, point it at an organization with a sensible balance, and bound the agent’s loops. ### Prerequisites [Section titled “Prerequisites”](#prerequisites) * OpenClaw installed and runnable (`openclaw --version`). * **Node.js 18 or newer** (the bridge uses the built-in `fetch`). * An iSearchFrom **API key** scoped to search, from an organization with the **Search Engine Proxy** feature and available token balance. Create one at [API Keys](https://app.isearchfrom.com/settings/api-keys) — copy the secret immediately, it’s shown once. ### Build the bridge [Section titled “Build the bridge”](#build-the-bridge) ```bash mkdir isearchfrom-mcp && cd isearchfrom-mcp npm init -y npm pkg set type=module npm install @modelcontextprotocol/sdk zod ``` Create `server.js`: ```js #!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; const BASE_URL = process.env.ISEARCHFROM_BASE_URL ?? 'https://api.isearchfrom.com/v1'; const API_KEY = process.env.ISEARCHFROM_API_KEY; if (!API_KEY) { console.error('ISEARCHFROM_API_KEY is not set'); process.exit(1); } async function api(path, init = {}) { const res = await fetch(`${BASE_URL}${path}`, { ...init, headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, ...(init.headers ?? {}) } }); const body = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(`iSearchFrom API ${res.status}: ${JSON.stringify(body)}`); } return body; } const server = new McpServer({ name: 'isearchfrom', version: '1.0.0' }); server.tool( 'serp_proxy_search', 'Run a localized Google or Bing search from a chosen country, city, and device, then return the parsed organic and ad results.', { query: z.string().describe('Search query, worded as a real user would type it.'), country: z.string().describe('ISO country code, e.g. "US", "GB", "BR".'), language: z.string().describe('Language code, e.g. "en", "pt".'), engine: z.string().default('google').describe('Search engine: "google" or "bing".'), device: z.string().default('desktop').describe('Device: "desktop" or "mobile".'), city: z.string().optional().describe('Optional city-level location.'), pages: z .array(z.number().int().min(1).max(10)) .default([1]) .describe('Result pages to capture (1-10).') }, async (args) => { // 1. Create the search. It runs asynchronously server-side. const search = await api('/tools/serp/proxy-search', { method: 'POST', body: JSON.stringify(args) }); // 2. Poll until the search finishes, fails, or times out. const deadline = Date.now() + 60_000; let current = search; while (current.status === 'pending' || current.status === 'running') { if (Date.now() > deadline) break; await new Promise((resolve) => setTimeout(resolve, 1500)); current = await api(`/tools/serp/proxy-search/${search.id}`); } return { content: [{ type: 'text', text: JSON.stringify(current, null, 2) }], isError: current.status === 'failed' }; } ); await server.connect(new StdioServerTransport()); ``` Add more tools The bridge exposes one tool, `serp_proxy_search`. Add more `server.tool(...)` blocks that wrap other **read** endpoints — position tracker, ad tracker, or AI-visibility analytics — using the same `api()` helper. Avoid wrapping destructive endpoints (create, update, delete) for an autonomous agent unless a human reviews those actions. See the [API Overview](/reference/api/). ### Register the bridge as a stdio server [Section titled “Register the bridge as a stdio server”](#register-the-bridge-as-a-stdio-server) ```json { "mcp": { "servers": { "isearchfrom": { "command": "node", "args": ["/absolute/path/to/isearchfrom-mcp/server.js"], "env": { "ISEARCHFROM_API_KEY": "isk_your_key_here" } } } } } ``` Keep the path absolute and the key private `args` must be the **absolute** path to `server.js` — run `pwd` in the `isearchfrom-mcp` folder and append `/server.js`. Your key sits in `openclaw.json` in plain text, so lock the file down: ```bash chmod 600 ~/.openclaw/openclaw.json ``` Never commit it to source control. ## Security and safe use [Section titled “Security and safe use”](#security-and-safe-use) Prefer the hosted server Option A keeps your credentials out of config files entirely and gives you live, revocable, scope-limited access. Reach for the bridge only when you specifically need stdio. Least-privilege keys (bridge) If you use the bridge, give the key only the search permission it needs, and use a separate key per integration so you can rotate or revoke it independently. Expose only read tools Keep an autonomous agent’s tools search-and-read only; don’t wrap create, update, or delete for unattended loops. Mind tokens and rate limits Proxy searches consume tokens; the API’s default limit is 600 requests per minute. Bound agent loops so a runaway plan can’t drain the balance. See [Errors, pagination & rate limits](/reference/errors-pagination-rate-limits/). ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Symptom | Likely cause | Fix | | ------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------- | | Tool not listed in `mcp status` | Server failed to start / connect | For Option A, recheck the URL and re-authorize. For the bridge, run `node server.js` directly. | | Browser sign-in never opens (A) | Client didn’t complete OAuth | Confirm the URL is `https://api.isearchfrom.com/mcp` and your OpenClaw version supports HTTP MCP. | | `401` / API key invalid (B) | Wrong, disabled, or expired key | Recreate the key in [API Keys](https://app.isearchfrom.com/settings/api-keys). | | `403` / forbidden | Missing permission or feature | Grant the search permission; confirm the org has the Search Engine Proxy feature. | | `429` / too many requests | Rate limit exceeded | Slow the agent; the default limit is 600 requests per minute. | | Empty or failed results | No token balance or unsupported options | Check the balance and review [Search Parameters](/reference/search-parameters/). | ## Related [Section titled “Related”](#related) [MCP Server Overview](/mcp/overview/)The hosted server, its tools, and its security model. [Connect a client](/mcp/connect/)Setup for Claude, Cursor, Cline, and generic clients. [Authentication and API Keys](/reference/authentication-api-keys/)Create, scope, and rotate API keys for the bridge. [Search Parameters](/reference/search-parameters/)Country, city, language, device, and SafeSearch options. # Connect Google Search Console > Bring Google's own view of your search performance into iSearchFrom, and compare it with position checks made from a real city, device and language. Search Console tells you what Google says you rank for, averaged across everyone who searched. iSearchFrom tells you where you appear from one specific place, device and language. Seeing both is the point. ## Connect [Section titled “Connect”](#connect) 1. **Open Settings → Integrations.** 2. **Press Connect on the Search Console card.** 3. **Grant access.** Unlike Google Ads, Search Console has a genuine read-only permission — the consent screen asks only to *view* your data, and that is all the access exists to give. 4. **Add a property.** Every property your Google account can read is offered; pick the ones you care about. Properties you can see but not read data for are filtered out. ## What you get [Section titled “What you get”](#what-you-get) For each property, the top queries over the last 28 days: | Column | What it means | | ------------- | ----------------------------------------- | | Query | What people typed to reach you | | Clicks | How many people came | | Impressions | How often you appeared | | CTR | Clicks divided by impressions | | Avg. position | Google’s averaged position for that query | ## Reported next to measured [Section titled “Reported next to measured”](#reported-next-to-measured) Once a property is added, the query table shows both numbers side by side: | Column | Meaning | | ----------- | ----------------------------------------------------------------------- | | Google avg. | Google’s averaged position for that query | | Measured | The position we checked, from one city and device | | Gap | The difference, signed. `+2.4` means you rank **better** where we check | Queries Google reports that you track nothing for are listed too, with an empty Measured column — usually the most useful row on the screen, because it is a query bringing you impressions that nobody is watching. ## Track what Google already told you matters [Section titled “Track what Google already told you matters”](#track-what-google-already-told-you-matters) Every row with an empty **Measured** column is a query bringing you impressions that nothing is watching. Tick the ones worth following and press **Track in position tracker**, or use **Select all untracked** to take the lot. That opens the ordinary campaign wizard with the queries, the site, and a starting country and device already filled in — the property names its own domain, and Google reports where its impressions come from, so the wizard opens on the country and device most of your audience actually uses. The share behind each choice is shown beside the button, and recorded in the campaign description, so the default reads as evidence rather than a guess. Every field is still yours to change: location, device, language and schedule are where you decide what “where do I rank” actually means, and nothing is created until you finish the wizard. The campaign name follows the site, place and device you settle on until you edit it yourself. Start with the gaps, not the winners A query you already rank first for has little room to move. The interesting rows are the ones with real clicks and an average position deep enough to be worth measuring from a specific place — that is where Google’s average and a real check are most likely to disagree. ## Why the position differs from ours [Section titled “Why the position differs from ours”](#why-the-position-differs-from-ours) This is the most useful thing on the page, and it looks like a bug the first time you see it. **Search Console averages one number across everyone**: every city, every device, every language, every result type, over the whole period. **A tracker check is one measurement**: this keyword, in this city, on this device, in this language, right now. So an average position of 8.4 in Search Console and a tracked position of 3 in Vilnius are not in conflict — they are two true statements. The gap between them is usually the interesting part: * **Tracked much better than the average** — you are strong locally and weaker elsewhere. * **Tracked much worse than the average** — the place you are tracking is not where your audience is. Data lags a couple of days Search Console finalises data 24–72 hours behind. iSearchFrom ends its window three days back so the last day shown is complete — otherwise the final point looks like a cliff, and reads as a drop rather than as lag. ## Limits worth knowing [Section titled “Limits worth knowing”](#limits-worth-knowing) * **Your own properties only.** Search Console has no competitor data, by design. * **16 months of history** is all Google keeps. * **Not every row.** Google withholds queries below a privacy threshold. Google Ads Connect Google Ads to import live campaigns straight into position and ad tracking. Disconnect any time Settings → Integrations. Revokes iSearchFrom’s access at Google immediately. # Marketplace Alerts > What fires, what deliberately does not, and how the thresholds work. Alerts watch a campaign run to run and tell you when something moved enough to be worth reading. They are evaluated **per target**, so a campaign watching four shelves can report four independent stories. ## What fires [Section titled “What fires”](#what-fires) | Alert | When | Severity | | ------------------ | ------------------------------------------------------------------------ | ------------------ | | Entered the top 10 | Your best position on a target crossed into the top 10 | Success | | Left the top 10 | It crossed back out | Error | | Rank changed | Your position moved by 3 places or more | Success or warning | | Price changed | A listing on the shelf repriced by 10% or more — yours or a competitor’s | Info or warning | Price alerts are the ones a rank tracker usually cannot give you. A competitor undercutting you by 15% is at least as actionable as a rank slip, and it is often the cause of one. ## What deliberately does not fire [Section titled “What deliberately does not fire”](#what-deliberately-does-not-fire) Most of the design here is about **not** sending things. **A single bad page.** Each run is one page load, and pages disagree. A product sitting at 8, 9 and 25 across three runs is an 8–9 product that hit one bad page — so the window uses the **median** of each run’s measurement, not the average and not the best. The median says 9 and stays quiet; the average would say 14 and the best would say 8, and both would alert on the anomaly instead of the trend. **A new campaign.** Comparing a first run against nothing would fire “entered the top 10” for every product you track, on the day you created the campaign. Both sides of the comparison need at least two runs. **A stale baseline.** The comparison reaches back at most **45 days**. A campaign that ran three times last week and three times two months ago would otherwise compare this week against two months ago and report all the drift in between as one sudden move. **A target seen once.** If a target only appeared in one run of a window, it is skipped. That is a single page load, not a trend. **Small wobbles.** Under 3 positions, or under a 10% price move, nothing is sent. ## Where alerts arrive [Section titled “Where alerts arrive”](#where-alerts-arrive) | Channel | What arrives | | ----------------- | ------------------------------------------------------------------------------ | | In-app | Every alert, in the notification feed | | Email | Every alert, batched: at most one email per hour per organization | | Webhook | Every alert, if a webhook URL is set on the campaign | | Chat destinations | On plans with alert channels — see [Alert Channels](/tracking/alert-channels/) | Owners receive alerts in the app and by email by default; admins and members opt in under **My alerts**. Alert emails are batched per organization, the same way they are for every tracker: at most one email per hour, carrying every alert raised across all campaigns since the last one, grouped by campaign, so a run that moves five keywords is one email, not five. A lone alert can therefore take up to an hour to reach the inbox; the in-app feed and the webhook get it at once. Each person can turn alert emails off in their notification preferences. ## Turning them on [Section titled “Turning them on”](#turning-them-on) Alerts are configured per campaign, in the wizard’s schedule step. You can switch the top-10 transitions, rank changes and price moves on or off individually, keep the campaign out of the alert email with its “Email alerts” switch (the feed and the webhook are unaffected), and set a webhook URL for outbound delivery. Alerts only evaluate after a run that produced data — a run that failed entirely never triggers them. ## Related [Section titled “Related”](#related) * [Metrics and results](/marketplace/metrics-results/) — what the numbers behind an alert mean * [Schedules and alerts](/tracking/schedules-alerts/) — how scheduling works across all trackers * [Webhooks](/reference/webhooks/) — payload format and delivery # Create a Marketplace Campaign > Pick targets, keywords and the products you want tracked, then schedule the run. A campaign answers one question repeatedly: where do these products rank, for these searches, on these shelves. 1. **Name it and pick a project** Campaigns live inside a project, the same as every other tracker. If you have no project yet you can create one without leaving the wizard. 2. **Choose your targets** Pick the platforms, and for Amazon the storefronts. Up to **8 targets** per campaign. Every target is measured separately and every one multiplies the cost of a run, so pick the shelves you would actually act on rather than all of them. 3. **Add keywords** The searches a shopper types — not your product names. `cordless vacuum` tells you something; `Dyson V15 Detect Absolute 2023` mostly tells you that you rank for your own name. 4. **Tell it which listings are yours** Either product ids, brands, or both. At least one is required, otherwise nothing on the page can be identified as yours. 5. **Set the location and language** Country and language apply to the whole campaign. A delivery location is optional: a postcode, used by the Amazon storefront of the shopper country and by Walmart, and by Google Shopping as a place name. 6. **Schedule it, and choose alerts** Daily or weekly is usually right. Rank does not move fast enough hourly to be worth the tokens. ## Product ids and brands [Section titled “Product ids and brands”](#product-ids-and-brands) These are how a listing on the page is recognised as yours, and they behave differently: | | Matches on | Best for | | -------------- | -------------------------------------------- | ----------------------------------------------------------- | | **Product id** | Exactly one listing on one platform | Precision — an ASIN identifies one listing and nothing else | | **Brand** | Any listing whose title or brand contains it | Coverage — catches variants, bundles and renamed listings | Product ids are platform-specific An ASIN matches on Amazon only. A campaign watching Amazon and Google Shopping needs ids for both, or a brand — brands match on every target. This is the most common reason a target shows “no slot” while another one ranks fine. ### Finding product ids without leaving the wizard [Section titled “Finding product ids without leaving the wizard”](#finding-product-ids-without-leaving-the-wizard) Nobody has an ASIN to hand. Use **Find my product**: pick a target, type the product name, and the wizard searches that shelf live and shows you what it found. Click your listing and its id is added for you. This costs no tokens. You can paste the product page URL instead of the id: an Amazon `/dp/` link, a Walmart `/ip/` link or a Google Shopping product link is reduced to its id as you add it. An id that fits none of the chosen platforms, or a link the tool cannot read, is refused before the campaign is saved and before any token is spent. ## Storefronts [Section titled “Storefronts”](#storefronts) Amazon storefronts are separate targets because they are separate markets. The same keyword on `amazon.com` and `amazon.de` returns different products, in different currencies, at different prices, ranked differently. Google Shopping and Walmart are single-market: Google Shopping follows the campaign’s country, Walmart is US-only. Amazon storefronts on offer: `com`, `co.uk`, `de`, `fr`, `it`, `es`, `nl`, `se`, `pl`, `ie`, `ca`, `com.mx`, `com.br`, `com.au`, `co.jp`, `in`, `ae`, `sa`, `com.tr`. Belgium, Singapore, Egypt and China are not offered: our network cannot read a ranked shelf there. Saudi Arabia (`sa`) is served over a single, slower route and a check there typically takes 10 to 25 seconds rather than a few. Google Shopping is offered in the countries where Google runs a Shopping tab; the country picker only lists those once a Google Shopping target is chosen. ## Sharing a report [Section titled “Sharing a report”](#sharing-a-report) Every campaign page has a **Share** button. Switching the public link on publishes a read-only report of the campaign’s shelves and rankings on a link anyone can open without signing in: the latest run’s best position and share of shelf per storefront, the keyword-by-storefront matrix, and each shelf with the tracked listing highlighted. Switch it off at any time and the link stops working immediately. The report never shows the owner, the organisation, or the tokens spent. ## Why a check fails [Section titled “Why a check fails”](#why-a-check-fails) A check fails when every route through our network refused or returned nothing for that keyword on that target. The common reasons, all avoidable at setup: * A **city name as delivery location**. Amazon and Walmart take postcodes only. * A **storefront the tool no longer offers** on an older campaign. Such targets are skipped at run time and cost nothing; edit the campaign to drop them. * **Google Shopping from a country without a Shopping tab**. The page answers with ordinary web results and no shelf. * A keyword with pasted control characters or doubled spaces. These are cleaned before sending, but a keyword that is only punctuation returns nothing. ## Delivery location [Section titled “Delivery location”](#delivery-location) Where a target supports it, the delivery location changes the ranking rather than just the shipping estimate — a marketplace reorders by what it can actually ship to that address. Amazon and Walmart locate by **postcode**, never by city name, and a postcode belongs to one country: it is applied to the Amazon storefront of the shopper country and to Walmart when the shopper is in the US. Other storefronts in the same campaign are searched nationally. Google Shopping takes a place name and is only offered in the countries where Google runs a Shopping tab. Leave it empty for national results. If none of your targets read a delivery location, the field is hidden rather than silently ignored. ## Editing later [Section titled “Editing later”](#editing-later) Targets **can** be changed after creation. Adding one starts measuring a new shelf from the next run; removing one stops future runs but leaves its history intact, because every check records the target it actually queried rather than reading it back off the campaign. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Marketplace Visibility](https://app.isearchfrom.com/tools/ecommerce/marketplace) and choose **New campaign**. [Metrics and results](/marketplace/metrics-results/)What the numbers mean once the first run finishes. # Marketplace Metrics and Results > Read share of shelf, best position, ad pressure, price standing, placements and the competitor table. Results are grouped by **target**, always. Each block answers for one shelf, and there is no blended figure across them — see [why](/marketplace/overview/). ## Key metrics [Section titled “Key metrics”](#key-metrics) | Metric | Meaning | | --------------------- | ------------------------------------------------------------------------------ | | Best position | The highest slot any of your listings held for that keyword | | Share of voice | How much of the ranked shelf you hold, weighted so higher positions count more | | Ad pressure | Sponsored slots standing **above** your best organic listing | | Price standing | Where your price sits on that shelf, 0 = cheapest, 1 = dearest | | Sponsored | Ads counted on the page, or **Not measured** | | Ranked / returned | Rows that held a numbered slot, out of everything the page returned | | Keywords with no slot | Keywords where you hold no ranked position at all | ### Ad pressure is the one to watch [Section titled “Ad pressure is the one to watch”](#ad-pressure-is-the-one-to-watch) Rank alone under-describes a shelf. Ranking fourth sounds fine until five sponsored listings sit above you, at which point you are the ninth thing a shopper sees. Ad pressure counts exactly that: sponsored slots above your best **organic** position. It is **Not measured** when the surface cannot see ads on that check, and when you hold no organic slot to be pressured in the first place. ### Price standing [Section titled “Price standing”](#price-standing) A number from 0 to 1 placing your price against every other priced listing on the same shelf: * **0** — cheapest on the shelf * **1** — dearest on the shelf * **Not measured** — your listing carried no price, or the shelf had fewer than two priced listings Read it next to rank. Ranking twentieth at the dearest end of the shelf is a pricing problem; ranking twentieth at the cheapest end is a listing problem. ### Ranked versus returned [Section titled “Ranked versus returned”](#ranked-versus-returned) `22 / 28` means the page returned 28 rows and 22 of them held a numbered slot. The other six were carousels, banners or recommendation strips — real placements a shopper sees, but not part of the ranking. Share of voice is measured against the **ranked** count, because that is the shelf you are competing on. ## Placements [Section titled “Placements”](#placements) Every stored listing is one of two placements: | Placement | Meaning | | ------------ | ----------------------------------------------------------------------------- | | **Ranked** | Held a numbered slot in the result set | | **Carousel** | Appeared on the page with no slot — a recommendation strip or sponsored shelf | Carousel rows keep no position, ever. A position is only ever the one the page actually gave. > Measured on a live Amazon page: 35 rows, 19 of them sponsored, and only 3 of those 19 carried a position. Numbering the other 16 from their order in the list would have made that page look three times more contested than it was. ## Overview tab [Section titled “Overview tab”](#overview-tab) One block per target, each with its keyword table. Use it to answer: * Which shelves are we winning, and which are we absent from? * Where is ad pressure highest? * Are we priced out, or ranked out? * How many keywords hold no slot at all? Click any keyword row to open its listings. ## Results tab [Section titled “Results tab”](#results-tab) The full result set for one keyword on one target, in page order, for a single run. Your listings are highlighted; sponsored rows are labelled; carousel rows are marked as such rather than given a fake position. Product titles link to the listing on the marketplace itself, so a rank can be checked against the real page. ## Trend tab [Section titled “Trend tab”](#trend-tab) Rank history for one keyword on one target — the only slice a rank series is honest about. Each point carries best position, share of voice, ad pressure, **your price** and the cheapest price on the shelf. Price rides the same series as rank on purpose: a rank slip the week you raised your price is a different story from one you cannot explain. ## Competitors tab [Section titled “Competitors tab”](#competitors-tab) Every product that ranked for your keywords, ordered by **how often it was ahead of you** rather than by how often it appeared. | Column | Meaning | | ------------- | --------------------------------------------------------- | | Ahead of you | Times it outranked your best listing for the same keyword | | Appearances | Times it appeared at all | | Best position | The highest slot it reached | | Sponsored | How often it paid for the placement rather than earned it | A keyword where you rank nowhere counts as outranked — because it is. ## Runs [Section titled “Runs”](#runs) A run is bounded: a check that has not settled two hours after it started is failed and its token refunded, and the run closes as partial. Most runs finish in seconds; a reading that arrives late is picked up by itself within minutes. Each run records how many checks it made and how many failed. A failed check is a check that could not be answered, not a check that found nothing: “no listings returned” and “the page could not be read” are recorded differently. # Marketplace Visibility Overview > Track where your products rank on Amazon, Google Shopping and Walmart — organic and sponsored, per storefront. Marketplace Visibility shows where your products sit on the shelf a shopper actually sees — across several marketplaces at once, with the sponsored slots above you counted separately from the organic ones. ## Why several marketplaces at once [Section titled “Why several marketplaces at once”](#why-several-marketplaces-at-once) A product’s rank only means something next to the other shelves it sits on. Ranking third on Amazon while sitting twentieth on Google Shopping for the same search is a different problem from ranking third everywhere, and you cannot see the difference from one marketplace alone. A campaign therefore watches **targets** rather than a single platform. A target is one platform plus one storefront — `Amazon (de)` and `Amazon (com)` are two different targets, because the same keyword returns different products, prices and currencies on each. Rank is never merged across targets Position 3 on Amazon and position 3 on Google Shopping are not the same shelf. Results are kept separate per target throughout, and there is deliberately no blended “average rank” — it would be the number most likely to be quoted and least likely to mean anything. ## What it tracks [Section titled “What it tracks”](#what-it-tracks) Organic and sponsored rank Where your products appear, with ads counted separately rather than blended into one figure. Ad pressure How many sponsored slots stand above your best organic listing — the ones a shopper scrolls past before reaching you. Price standing Where your price sits on the shelf you are ranking against, from cheapest to dearest. Share of shelf How much of the visible result set your listings hold, weighted so higher positions count for more. Competitors ahead of you Who outranks you, how often, and whether they paid for the placement. Rank and price history One series per keyword per target, so a rank move and a price change can be read together. ## Supported platforms [Section titled “Supported platforms”](#supported-platforms) | Platform | Storefronts | Rank | Sponsored | Delivery location | | ------------------- | ------------------------- | ---- | --------- | ----------------- | | **Amazon** | 19 storefronts by TLD | Yes | Yes | Yes | | **Google Shopping** | Single market, by country | Yes | Sometimes | Yes | | **Walmart** | Single market (US) | Yes | Yes | No | ### “Sometimes”, and why it is not zero [Section titled ““Sometimes”, and why it is not zero”](#sometimes-and-why-it-is-not-zero) Some surfaces cannot report sponsorship on every check. Where that happens the tool shows **Not measured** rather than **0**, everywhere it appears. That distinction is deliberate. “No ads ran on this page” and “ads could not be seen on this check” are different facts, and collapsing them into a zero would quietly report a clean shelf on a surface nobody looked at. Google Shopping is the surface where you will see this most often. Reading a “Not measured” It never means the number is missing by accident. It means the check cannot answer that question honestly, so it declines to. ## What a run costs [Section titled “What a run costs”](#what-a-run-costs) A **check** is one keyword on one target. Targets therefore multiply a run rather than add to it: > 10 keywords × 4 targets = **40 checks**, and 40 tokens. Adding a fifth target to a ten-keyword campaign costs the same again as the first ten keywords did. The campaign editor shows the total before you save. ## Where to go next [Section titled “Where to go next”](#where-to-go-next) [Create a campaign](/marketplace/create-campaign/)Pick targets, keywords and the products you want tracked. [Metrics and results](/marketplace/metrics-results/)Read share of shelf, ad pressure, price standing and the competitor table. [Alerts](/marketplace/alerts/)What fires, what deliberately does not, and how the thresholds work. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Marketplace Visibility](https://app.isearchfrom.com/tools/ecommerce/marketplace) in the app. # Connect a client > Step-by-step setup to connect Claude Code, Claude Desktop, Cursor, Cline, OpenClaw, Hermes Agent, or any MCP-compatible client to the iSearchFrom MCP server, including the one-time OAuth sign-in. Point your client at `https://api.isearchfrom.com/mcp`, complete a one-time browser sign-in, and your agent can use iSearchFrom’s tools. The steps below cover the most common clients; any spec-compliant MCP client that supports Streamable HTTP + OAuth works the same way. ## Before you start [Section titled “Before you start”](#before-you-start) * Any iSearchFrom account works: the MCP server is included on every plan, the free plan too. What the agent can do follows your plan, so the tracking tools appear once you’re on a paid plan. See [Plans, tokens & limits](/guides/plans-tokens-limits/). * The **server URL is the same for every client**: `https://api.isearchfrom.com/mcp`. * There is **no API key to paste** — the client discovers the OAuth endpoint from the URL and opens a browser for you to sign in and pick an organization. ## Set up your client [Section titled “Set up your client”](#set-up-your-client) If your client supports one-click install, use a button below — it pre-fills the server URL for you. Otherwise follow the per-client steps that follow. [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=isearchfrom\&config=eyJ1cmwiOiJodHRwczovL2FwaS5pc2VhcmNoZnJvbS5jb20vbWNwIn0%3D)[Add to VS Code](https://vscode.dev/redirect/mcp/install?name=isearchfrom\&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.isearchfrom.com%2Fmcp%22%7D)Copy Claude Code commandCopied * Claude Code Add the server with the Claude Code CLI: ```bash claude mcp add --transport http isearchfrom https://api.isearchfrom.com/mcp ``` The first time a session uses a tool, Claude Code opens your browser to sign in and authorize. Verify it registered: ```bash claude mcp list ``` You should see `isearchfrom` listed. Remove it any time with: ```bash claude mcp remove isearchfrom ``` * Claude Desktop Recent Claude Desktop versions support remote servers as **custom connectors**: ### Open connector settings [Section titled “Open connector settings”](#open-connector-settings) Go to **Settings → Connectors → Add custom connector**. ### Add the server [Section titled “Add the server”](#add-the-server) Give it a name (`iSearchFrom`) and paste the URL `https://api.isearchfrom.com/mcp`. ### Authorize [Section titled “Authorize”](#authorize) Claude Desktop opens a browser window. Sign in, choose the organization the agent should act in, and approve the permissions. The connector then shows as connected. Older versions If your version has no custom-connector UI, use Claude Code or another client below — the hosted server is identical regardless of client. * Cursor Add the server to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project: ```json { "mcpServers": { "isearchfrom": { "url": "https://api.isearchfrom.com/mcp" } } } ``` Open **Cursor Settings → MCP**, confirm `isearchfrom` appears, and click to authorize when prompted. Cursor opens the browser sign-in and stores the resulting token for you. * Cline In VS Code, open the Cline MCP settings (**Cline → MCP Servers → Configure**) and add a remote server: ```json { "mcpServers": { "isearchfrom": { "url": "https://api.isearchfrom.com/mcp", "transportType": "streamableHttp" } } } ``` Reload the server list; Cline prompts you to authorize in the browser on first use. * OpenClaw Add the server with the OpenClaw CLI. OpenClaw defaults remote servers to SSE, so set the streamable HTTP transport and OAuth explicitly: ```bash openclaw mcp add isearchfrom --url https://api.isearchfrom.com/mcp --transport streamable-http --auth oauth openclaw mcp login isearchfrom ``` `login` opens the browser sign-in. See the [OpenClaw guide](/integrations/openclaw/) for the config-file version and troubleshooting. * Hermes Agent Add the server to `~/.hermes/config.yaml`: ```yaml mcp_servers: isearchfrom: url: "https://api.isearchfrom.com/mcp" auth: oauth ``` Then sign in and check the connection: ```bash hermes mcp login isearchfrom hermes mcp test isearchfrom ``` On a headless machine, use `hermes mcp login isearchfrom --flow device`. Inside a running session, `/reload-mcp` picks up the new server. * Any MCP client For any spec-compliant client that supports **Streamable HTTP** transport and **OAuth**: * **Server URL:** `https://api.isearchfrom.com/mcp` * The client should perform OAuth discovery against `https://api.isearchfrom.com/.well-known/oauth-protected-resource`, register itself, and open a browser for you to sign in and consent. * No client secret or API key is required — the server issues a token scoped to the organization and permissions you approve. If your client cannot do the OAuth flow (for example, a stdio-only agent), see the [OpenClaw guide](/integrations/openclaw/) for a bridge pattern. ## The one-time authorization [Section titled “The one-time authorization”](#the-one-time-authorization) However you add the server, the first tool call triggers the same flow: ### Sign in [Section titled “Sign in”](#sign-in) The client opens `https://app.isearchfrom.com` in your browser. Sign in if you aren’t already. ### Choose an organization [Section titled “Choose an organization”](#choose-an-organization) If you belong to more than one organization, pick which one the client should act in. Every tool the agent runs happens inside that organization and spends its tokens. ### Approve the permissions [Section titled “Approve the permissions”](#approve-the-permissions) The consent screen shows exactly what the client is asking for. Approve to finish, or cancel to abort. The client stores the resulting token and reuses it for future sessions. Grant the least you need If you only want the agent to run searches and read results, approve a read/search-scoped grant and nothing more. The agent physically cannot call tools outside its grant, and you can [revoke the connection](/mcp/security/#revoke-a-connection) at any time. ## Verify it works [Section titled “Verify it works”](#verify-it-works) Ask your agent something that exercises a read-only tool first — it’s free and confirms the connection end to end: ```text Using iSearchFrom, who am I connected as and what's my current token balance? ``` The agent calls `whoami` and `get_token_balance` and reports your user, organization, and balance. Then try a real search: ```text Using iSearchFrom, run a Google search for "best crm software" as a mobile user in Germany (country DE, language de) and summarise the top 3 organic results and any ads. ``` ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) | Symptom | Likely cause | Fix | | ------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Browser sign-in never opens | Client didn’t complete OAuth discovery | Confirm the URL is exactly `https://api.isearchfrom.com/mcp`; check the client supports HTTP + OAuth. | | “Feature not enabled” after sign-in | Organization’s plan doesn’t include MCP | Enable MCP on the plan or switch to an organization that has it. See [Plans, tokens & limits](/guides/plans-tokens-limits/). | | Agent can’t see a tool you expected | Your grant or role doesn’t include it | Re-authorize with the needed permission, or check your role. See [Permissions & security](/mcp/security/). | | Tool returns “no read access granted” | `query_sql`/`describe_schema` need read scope | Grant read on the relevant data when you authorize the client. | | Searches return errors about balance | Not enough tokens | Top up or check usage — see [Token top-ups](/billing/token-top-ups/). | | `429` / rate limited | Agent looped too fast | Bound the agent’s loops; limits are documented in [Errors, pagination & rate limits](/reference/errors-pagination-rate-limits/). | ## Related [Section titled “Related”](#related) [Tool catalog](/mcp/tools/)Every tool the agent can call. [Permissions & security](/mcp/security/)Scopes, token spend, and revoking a connection. [OpenClaw](/integrations/openclaw/)Connect a self-hosted OpenClaw agent. # MCP Server Overview > Connect Claude, Cursor, Cline or any MCP-compatible AI agent to iSearchFrom's free MCP server for localized search, rank tracking, and AI-visibility data over the Model Context Protocol. iSearchFrom runs a **free, hosted, first-party MCP server**, included on every plan (the free plan too). Point any MCP-compatible AI client at it, authorize once, and your agent can run localized Google and Bing searches, manage rank-tracking and ad-tracking campaigns, read AI-visibility results, and query your organization data, all with permissions you control and enforced on every call. The [Model Context Protocol](https://modelcontextprotocol.io) (MCP) is the open standard for connecting AI agents to external tools and data. Instead of copying results into a chat by hand, an MCP-connected agent discovers iSearchFrom’s tools automatically and calls them while it reasons — “check my brand’s visibility on ChatGPT for these ten prompts,” “run this search as a mobile user in Germany and summarise the ads,” “add a rank-tracking campaign for these keywords.” ## Connection details [Section titled “Connection details”](#connection-details) Everything an MCP client needs to connect: | Setting | Value | | -------------- | --------------------------------------------------------------------------- | | **Server URL** | `https://api.isearchfrom.com/mcp` | | **Transport** | Streamable HTTP (stateless) | | **Auth** | OAuth 2.1 — you authorize the client and pick which organization it acts in | | **Discovery** | `https://api.isearchfrom.com/.well-known/oauth-protected-resource` | No API key, no local bridge The MCP server uses OAuth, not an API key. You do **not** need to build a local bridge, run a proxy, or paste a secret into a config file — a compliant client discovers the auth endpoint from the server URL and walks you through a one-time browser sign-in. See [Connect a client](/mcp/connect/). ## What your agent can do [Section titled “What your agent can do”](#what-your-agent-can-do) The server exposes **89 tools** across eight areas. Read tools return data; action tools create, change, or run things (and may spend tokens). Your agent only ever sees the tools your [granted permissions](/mcp/security/) allow. Search Run localized proxy and direct searches; read the parsed organic results, ads, and metadata. Rank & ad tracking Create, update, delete, and trigger position-tracker and ad-tracker campaigns; read run status. Maps visibility Run geo-grid campaigns on Google Maps; read the rank heatmap, the Local Presence Score, and which competitors hold each grid point. Marketplace visibility Track where your products rank on Amazon, Google Shopping and Walmart; read shelf position, price, and sponsored share against competing listings. AI visibility Manage AI-visibility campaigns and read brand-mention metrics across ChatGPT, Gemini, Perplexity, and Google AI surfaces. Projects List, create, update, and delete the projects that group your searches and campaigns. Flexible data queries `describe_schema` + `query_sql` let the agent run read-only SQL over your own organization’s data, isolated at the database. Usage & identity `whoami`, token balance, entitlements, and token transaction history — so the agent can check budget before spending. See the full [Tool catalog](/mcp/tools/) for every tool, its purpose, and whether it costs tokens. Prefer not to wire up an agent? [AI Data Chat](/data-chat/overview/) is the same capability inside the app — the same tables, the same read-only role, the same isolation, answered by our model instead of yours. MCP is the right choice when you already work in an agent or want to combine this data with other sources; Data Chat is the right choice when you just want the answer. ## How authorization works [Section titled “How authorization works”](#how-authorization-works) MCP access is **user-scoped and least-privilege by design** — this is a deliberate security property, not an afterthought: 1. **You authorize a client once.** The client opens a browser sign-in; you approve it and pick the organization it should act in. 2. **You grant a subset of your own permissions.** The client can never do more than you can, and you can grant it less. A read-only grant blocks every write tool automatically — the agent won’t even see them. 3. **Every call is re-checked live.** On each tool call the server intersects the consent-time grant with your *current* role permissions. If your access is reduced later, the client’s access drops with it immediately — no re-consent needed. 4. **The organization is locked at the database.** Read queries run as a non-owner database role with row-level security bound to your organization, so an agent can only ever see your own data. Full detail — scopes, token spend, and how to revoke a connection — is in [Permissions & security](/mcp/security/). ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * An iSearchFrom account. The MCP server is **free on every plan**, the free plan included. What your agent can do follows your plan, so the tracking tools appear once you’re on a paid plan. See [Plans, tokens & limits](/guides/plans-tokens-limits/). * Enough **token balance** for the actions you intend to run. Searches and campaign runs consume tokens exactly as they do in the app — see [Token usage](/billing/token-usage/). * An MCP-compatible client. [Connect a client](/mcp/connect/) has copy-paste setup for Claude Code, Claude Desktop, Cursor, Cline, and generic clients. ## Next steps [Section titled “Next steps”](#next-steps) [Connect a client](/mcp/connect/)Copy-paste setup for Claude Code, Claude Desktop, Cursor, Cline, and any MCP client. [Tool catalog](/mcp/tools/)Every tool the server exposes, grouped by area, with token-cost notes. [Permissions & security](/mcp/security/)Scopes, the consent flow, token spend, and revoking a connection. [REST API](/reference/api/)Prefer to call the API directly from your own code? Start with the API overview. # Permissions & security > How the iSearchFrom MCP server scopes what an agent can do — the consent flow, live permission checks, the isolated query layer, token spend, and revoking a connection. An MCP-connected agent can never do more than **you** can, and usually should do far less. Access is the intersection of what you approved at connect time and what your role still allows right now, re-checked on **every** tool call. ## Two boundaries, both enforced server-side [Section titled “Two boundaries, both enforced server-side”](#two-boundaries-both-enforced-server-side) What an agent can do is bounded by two independent gates. A tool call must pass **both**: 1. **The consent grant** — the permissions you approved when you [authorized the client](/mcp/connect/). This is a ceiling you choose. Approve read-only, and every action tool disappears from the agent’s view. 2. **Your live role permissions** — your current access in the organization. If your role is reduced after you authorized the client, the agent’s access shrinks with it on the very next call. No re-consent, no stale permissions. The effective permission set is the **intersection** of the two. Neither the client nor the agent can widen it — the checks run on iSearchFrom’s servers, not in the client. Why this matters This is the same least-privilege model the rest of the platform uses. Connecting an AI agent doesn’t create a new, broader kind of access — it’s a scoped-down view of your own account that you can tighten or cut off at will. ## The consent screen [Section titled “The consent screen”](#the-consent-screen) When a client connects for the first time, iSearchFrom opens a browser consent screen where you: * **Sign in** as yourself. * **Choose the organization** the client will act in. Every tool call happens inside that organization and spends its tokens. * **See exactly what’s requested** and approve or cancel. The client receives a token bound to that organization and grant. It reuses the token for later sessions until you revoke it. ## The organization is locked at the database [Section titled “The organization is locked at the database”](#the-organization-is-locked-at-the-database) The flexible [query layer](/mcp/tools/#flexible-data-queries) (`describe_schema` and `query_sql`) never touches the same database connection the app uses. It runs as a dedicated **non-owner, read-only database role** with **row-level security** that pins every query to your organization. ### The query layer [Section titled “The query layer”](#the-query-layer) Concretely, `query_sql`: * Runs inside a **read-only transaction** — no writes, no schema changes, ever. * Is bound to your organization via row-level security — it physically cannot return another organization’s rows. * Can only read a **curated set of columns** on a curated set of tables. Sensitive tables (users, sessions, API keys, billing internals, raw scraped HTML) are never exposed. * Is **capped to 100 rows** per query and has a short statement timeout, so an agent can’t run away with an expensive scan. * Only sees tables you were **granted read on** — `describe_schema` advertises exactly that subset. The result is a safe analytical window onto your own data. It’s powerful — the agent can answer novel questions with joins and aggregates — without being a security hole. ## Token spend [Section titled “Token spend”](#token-spend) MCP actions spend tokens identically to the app: * **Read tools are free** — `whoami`, `list_*`, `get_*`, `describe_schema`, `query_sql`, options tools. * **`run_proxy_search`** costs one token per requested page. * **`trigger_*_run`** costs tokens per keyword or prompt checked. * **`run_maps_campaign`** costs one token per grid point per keyword, plus one per keyword for the local pack — a 9×9 grid on 5 keywords is 410 tokens. * **`run_marketplace_campaign`** costs one token per keyword per target. * **`run_direct_search`, `create_*`, `update_*`, `set_*_campaign_status`, `delete_*`** are free. Autonomous agents can spend in a loop An agent that decides to run many searches will spend tokens for each one. Protect yourself: * Grant the **narrowest** scope the task needs. * Connect a client to an organization whose balance you’re comfortable exposing. * Have the agent call `get_token_balance` before batches, and bound its loops. * Watch spend with `get_token_transactions` or the app’s [token usage](/billing/token-usage/) view. ## Revoke a connection [Section titled “Revoke a connection”](#revoke-a-connection) You can cut off a client at any time — the agent loses access immediately on its next call. * Revoke the client’s authorization from your **account settings** in the app (the connected-apps / MCP connections view), or * Remove the server in the client itself (for example `claude mcp remove isearchfrom`), which stops it from calling — though revoking in iSearchFrom is the authoritative cut-off. Revoking is the right move if you no longer use a client, suspect a machine is compromised, or want to re-authorize with a tighter scope. ## Good practice [Section titled “Good practice”](#good-practice) Least privilege Grant only what the task needs. Read-only for analysis and reporting; add write scopes only when the agent genuinely needs to create or run campaigns. One client, one purpose Authorize separate clients for separate jobs so you can revoke one without disrupting the others. Human-in-the-loop for writes For autonomous agents, prefer read/search tools. Require a human to confirm before wrapping create, update, delete, or trigger tools into an unattended loop. Mind the balance Point agents at an organization with a sensible token balance, and monitor spend. ## Related [Section titled “Related”](#related) [Connect a client](/mcp/connect/)The authorization flow in detail. [Tool catalog](/mcp/tools/)What each permission unlocks. [Roles & permissions](/team-account/roles-permissions/)How organization roles define what you — and your agents — can do. # Tool catalog > Every tool the iSearchFrom MCP server exposes, grouped by area, with its type (read vs action), token cost, and the permission it requires. The MCP server exposes **89 tools**. Which ones your agent can see depends on the permissions you granted when you [authorized the client](/mcp/connect/) — a read-only grant hides every action tool. **Read** tools return data and are free; **Action** tools create, change, or run things and some spend tokens. Cost legend **Free** — no token cost. **Per page / per check / per point** — spends organization tokens exactly like the same action in the app; see [Token usage](/billing/token-usage/). An agent can call `get_token_balance` first to check budget before running paid tools. ## Identity & usage [Section titled “Identity & usage”](#identity--usage) Read-only. Use these so the agent can orient itself and check budget before spending. | Tool | Type | Cost | What it does | | ------------------------ | ---- | ---- | ------------------------------------------------------------------------ | | `whoami` | Read | Free | Returns the connected user and the organization the client is acting in. | | `get_token_balance` | Read | Free | Current token balance for the organization. | | `get_entitlements` | Read | Free | Which product features the organization’s plan has enabled. | | `get_token_transactions` | Read | Free | Recent token debits and credits, for auditing spend. | ## Projects [Section titled “Projects”](#projects) Projects group searches and campaigns. Managing them requires the **Project** permission. | Tool | Type | Cost | What it does | | ---------------- | ------ | ---- | ---------------------------------- | | `list_projects` | Read | Free | List projects in the organization. | | `get_project` | Read | Free | Fetch one project by id. | | `create_project` | Action | Free | Create a project. | | `update_project` | Action | Free | Rename or edit a project. | | `delete_project` | Action | Free | Delete a project. | ## Search [Section titled “Search”](#search) The flagship tools. Running a proxy search spends tokens; direct search only builds a URL and is free. Requires the **Proxy search** or **Direct search** permission and, for proxy search, the Search Engine Proxy feature. | Tool | Type | Cost | What it does | | ---------------------- | ------ | ------------ | ------------------------------------------------------------------------------------------------- | | `get_search_options` | Read | Free | Valid values for country, language, device, engine, SafeSearch, and AI platforms. | | `get_schedule_options` | Read | Free | Valid timezones, days, and cron format for campaign schedules. | | `run_proxy_search` | Action | **Per page** | Run a localized Google/Bing search and scrape the result pages. Async — poll `get_proxy_search`. | | `get_proxy_search` | Read | Free | Fetch a proxy search by id: status and, once complete, the parsed organic results and ads. | | `list_proxy_searches` | Read | Free | List past proxy searches, newest first: query, location, status and page count — not the results. | | `run_direct_search` | Action | Free | Build a localized search-engine URL and return it immediately (no scraping). | | `get_direct_search` | Read | Free | Fetch a direct search by id. | | `list_direct_searches` | Read | Free | List past direct searches, newest first, with their destination URL. | run_proxy_search is asynchronous `run_proxy_search` returns immediately with a `pending` status and an id. The agent polls `get_proxy_search` with that id until the status is `completed` (or `failed`). Well-behaved clients do this automatically. Each requested page consumes one token. ## Rank & ad tracking [Section titled “Rank & ad tracking”](#rank--ad-tracking) Create and run position-tracker and ad-tracker campaigns. Reading and managing campaigns requires the **Position tracker** or **Ad tracker** permission; triggering a run spends tokens per keyword checked. | Tool | Type | Cost | What it does | | ---------------------------------- | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `list_position_campaigns` | Read | Free | List position campaigns in the organization. | | `get_position_campaign` | Read | Free | Fetch one position campaign’s configuration. | | `create_position_campaign` | Action | Free | Create a SERP position-tracking campaign. | | `update_position_campaign` | Action | Free | Edit a position campaign. | | `delete_position_campaign` | Action | Free | Delete a position campaign. | | `trigger_position_run` | Action | **Per check** | Run a position campaign now. | | `list_position_runs` | Read | Free | List a campaign’s runs, newest first, with status and trigger (manual or scheduled). | | `get_position_run` | Read | Free | Fetch a position run’s status and results. | | `get_position_results` | Read | Free | Read captured rankings across a campaign. | | `get_position_overview` | Read | Free | Campaign analytics: average position, the top domains with their 1/7/14/30-day change, a per-keyword summary and the daily trend. | | `get_position_keyword_analytics` | Read | Free | One keyword’s ranking history: the domains ranking for it, their best/average position and change, and the daily positions. | | `get_position_alerts` | Read | Free | Alerts raised: a tracked domain entering or leaving the top 10, or a significant position change. | | `list_ad_tracker_campaigns` | Read | Free | List ad-tracker campaigns in the organization. | | `get_ad_tracker_campaign` | Read | Free | Fetch one ad campaign’s configuration. | | `create_ad_tracker_campaign` | Action | Free | Create an ad-tracking campaign. | | `update_ad_tracker_campaign` | Action | Free | Edit an ad campaign. | | `delete_ad_tracker_campaign` | Action | Free | Delete an ad campaign. | | `trigger_ad_tracker_run` | Action | **Per check** | Run an ad campaign now. | | `list_ad_tracker_runs` | Read | Free | List a campaign’s runs, newest first, with status and trigger (manual or scheduled). | | `get_ad_tracker_run` | Read | Free | Fetch an ad run’s status and results. | | `get_ad_tracker_results` | Read | Free | Read captured ad placements across a campaign. | | `get_ad_tracker_overview` | Read | Free | Campaign analytics: ads captured, the top advertisers with their 1/7/14/30-day change, a per-keyword summary and the daily trend. | | `get_ad_tracker_keyword_analytics` | Read | Free | One keyword’s ad history: the advertisers on it, their average position and change, the daily positions and the latest ad copy. | | `get_ad_tracker_alerts` | Read | Free | Alerts raised: a domain starting or stopping advertising on the tracked keywords. | Analytics come back capped `get_position_overview` and `get_ad_tracker_overview` return the 300 most recent daily trend rows and set `trendTruncated` when there were more. The keyword analytics tools return the top 50 domains (`domainsTruncated`) and the 300 most recent daily position rows (`positionHistoryTruncated`). Narrow the window with `dateFrom` / `dateTo`, or pass `domains`, to read past a cap. ## Maps visibility [Section titled “Maps visibility”](#maps-visibility) Track where a business ranks on the Google Maps canvas across a grid of coordinates, and how that compares to the local pack. Reading and managing campaigns requires the **Maps** permission. A run spends one token per check, which for Maps is **grid points × keywords, plus one per keyword for the local pack** — a sum, not a product. A 9×9 grid on 5 keywords is 410 tokens. Every campaign summary returns `tokensPerRun`, so check it before running. | Tool | Type | Cost | What it does | | -------------------------- | ------ | ------------- | --------------------------------------------------------------------------------------------------- | | `list_maps_campaigns` | Read | Free | List Maps campaigns, with cost per run. | | `get_maps_campaign` | Read | Free | Fetch one campaign’s grid geometry and config. | | `get_maps_heatmap` | Read | Free | Rank at every grid point, plus the Local Presence Score. | | `get_maps_competitors` | Read | Free | Who holds the grid: points won, avg rank, sponsored. | | `create_maps_campaign` | Action | Free | Create a geo-grid campaign. | | `update_maps_campaign` | Action | Free | Edit a campaign’s grid, keywords or surfaces. | | `set_maps_campaign_status` | Action | Free | Pause, resume or archive a campaign. Nothing is deleted. | | `delete_maps_campaign` | Action | Free | Permanently delete a campaign with its runs and history. | | `run_maps_campaign` | Action | **Per point** | Run a campaign now. | | `list_maps_runs` | Read | Free | List a campaign’s runs — the run id the heatmap and competitor tools need. | | `get_maps_run` | Read | Free | Fetch a run’s status; poll it after `run_maps_campaign`. | | `get_maps_trend` | Read | Free | Local Presence Score, top-10 coverage and average rank for one keyword across recent runs. | | `get_maps_alerts` | Read | Free | Alerts raised: entering or leaving the top 3 across the grid, or a big Local Presence Score change. | | `lookup_maps_place` | Read | Free | Resolve a Google Maps link or place identifier into the `trackedPlaceId` to track. | Track by place id, not name Ranking is measured against Google’s stable place id (`fid`). Businesses get renamed constantly, so a campaign without `trackedPlaceId` can silently lose its history. Set it when you can: `lookup_maps_place` reads a Google Maps link (a full address-bar URL or a **Share** short link), a Place ID (`ChIJ…`), a `0x…:0x…` feature id or a numeric CID and returns the `trackedPlaceId` that `create_maps_campaign` and `update_maps_campaign` take. It **cannot search by business name** — paste the Maps link of the business. It’s free, and `found` comes back false when the input carries no place identity. ## Marketplace visibility [Section titled “Marketplace visibility”](#marketplace-visibility) Rank and price tracking on Amazon, Google Shopping and Walmart. Reading requires the **Marketplace** permission; managing campaigns requires it too. A run spends one token per check, which is **keywords × targets** (a target is one platform plus one storefront). Every campaign summary returns `tokensPerRun`, so check it before running. Per-listing rows are read with `get_marketplace_results`. | Tool | Type | Cost | What it does | | --------------------------------- | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------ | | `list_marketplace_campaigns` | Read | Free | List marketplace campaigns: targets, keywords, tracked products. | | `get_marketplace_campaign` | Read | Free | Fetch one campaign’s config summary. | | `get_marketplace_overview` | Read | Free | Where the tracked products stand now, per target and keyword. | | `get_marketplace_keyword_trend` | Read | Free | Rank and price over time for one keyword on one target. | | `get_marketplace_competitors` | Read | Free | Which listings hold the shelf against yours. | | `create_marketplace_campaign` | Action | Free | Create a campaign. | | `update_marketplace_campaign` | Action | Free | Edit a campaign’s targets, keywords or tracked products. | | `set_marketplace_campaign_status` | Action | Free | Pause, resume or archive a campaign. Nothing is deleted. | | `delete_marketplace_campaign` | Action | Free | Permanently delete a campaign with its runs and history. | | `run_marketplace_campaign` | Action | **Per check** | Run a campaign now. | | `list_marketplace_runs` | Read | Free | List a campaign’s runs, newest first, with their status. | | `get_marketplace_run` | Read | Free | Fetch a run’s status; poll it after `run_marketplace_campaign`. | | `get_marketplace_results` | Read | Free | The listings a campaign saw, best rank first: title, rank, price, rating, seller — pass `runId` to read one shelf. | | `get_marketplace_alerts` | Read | Free | Alerts raised: a tracked product entering, leaving or moving in the top 10, and price changes on the shelf. | | `lookup_marketplace_product` | Action | Free | Search one target live for a product and return candidate listings with the id to track. | Find the product id before you create a campaign A campaign tracks products by the marketplace’s own id — an Amazon ASIN, or the platform’s own product id elsewhere. `lookup_marketplace_product` searches one target live by product name and returns up to 12 candidates with their `externalId`, ready to put in `trackedProductIds` of `create_marketplace_campaign` or `update_marketplace_campaign`. It spends **no tokens** and stores nothing, but it is rate limited to 20 lookups a minute. Ids differ between platforms and marketplaces, so run it once per target. ## AI visibility [Section titled “AI visibility”](#ai-visibility) Track how often your brand is mentioned by AI answer engines (ChatGPT, Gemini, Perplexity, Google AI Overviews, Google AI Mode). Requires the **AI visibility** permission; triggering a run spends tokens per prompt checked. | Tool | Type | Cost | What it does | | ------------------------------------ | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `list_ai_visibility_campaigns` | Read | Free | List AI-visibility campaigns in the organization. | | `get_ai_visibility_campaign` | Read | Free | Fetch one campaign’s prompts, brands, and competitors. | | `create_ai_visibility_campaign` | Action | Free | Create an AI-visibility campaign. | | `update_ai_visibility_campaign` | Action | Free | Edit an AI-visibility campaign. | | `delete_ai_visibility_campaign` | Action | Free | Delete an AI-visibility campaign. | | `trigger_ai_visibility_run` | Action | **Per check** | Run an AI-visibility campaign now. | | `get_ai_visibility_run` | Read | Free | Fetch an AI-visibility run’s status and metrics. | | `get_ai_visibility_results` | Read | Free | Read brand mentions and sentiment across a campaign. | | `list_ai_visibility_runs` | Read | Free | List a campaign’s runs, newest first, with checks run and failed. | | `get_ai_visibility_check` | Read | Free | One check in full: the answer the platform gave, every brand mentioned, and the comparison analysis. The answer text is cut at 20,000 characters. | | `get_ai_visibility_overview` | Read | Free | Headline analytics: visibility rate and trend, average position and sentiment, recommendation rate, share of voice, per-platform breakdown. Start here. | | `get_ai_visibility_trend` | Read | Free | Where and when the brand is visible: by prompt, location and platform, each with its change and daily series. | | `get_ai_visibility_competitors` | Read | Free | Who AI answers mention instead: share per domain, sentiment, head-to-head win rate and the attributes competitors are credited with. | | `get_ai_visibility_prompt_analytics` | Read | Free | Per-prompt grid: listed or missing on each platform, average position, competitor count, last check. | | `get_ai_visibility_ads` | Read | Free | Sponsored placements in AI answers: totals, ads per platform, which competitors advertise, and the prompts where they do and you do not. | | `get_ai_visibility_alerts` | Read | Free | Alerts raised for the campaign: a significant visibility change, your ads starting or stopping, a competitor starting to advertise. | | `suggest_ai_visibility_prompts` | Action | Free | Suggest prompts people might ask about a brand, or competitor domains, to seed a campaign. Generated text, not measured data. | ## Flexible data queries [Section titled “Flexible data queries”](#flexible-data-queries) Two read-only tools let the agent answer questions your canned tools don’t cover — “which keywords dropped the most last month?”, “how many searches did we run per country?” — by querying your own data directly. | Tool | Type | Cost | What it does | | ----------------- | ---- | ---- | ------------------------------------------------------------------------------------------ | | `describe_schema` | Read | Free | Lists the tables and columns the agent may query — scoped to the data you granted read on. | | `query_sql` | Read | Free | Runs a single read-only `SELECT` (or `WITH … SELECT`) over your organization’s data. | How query_sql stays safe `query_sql` runs as a **non-owner database role** inside a **read-only transaction** bound to your organization by row-level security. It cannot write, cannot reach other organizations’ data, cannot touch internal or sensitive tables, is capped to 100 rows, and times out quickly. It’s a safe analytical window onto **your** data — nothing more. See [Permissions & security](/mcp/security/#the-query-layer). ## Related [Section titled “Related”](#related) [Connect a client](/mcp/connect/)Set up your MCP client. [Permissions & security](/mcp/security/)How grants map to the tools above. [Token usage](/billing/token-usage/)Exactly what each paid action costs. # API Overview > Overview of the customer-facing iSearchFrom REST API. The iSearchFrom API lets teams automate selected organization workflows such as creating searches, listing search history, managing projects, reading token data, and working with tracker or AI Visibility campaign data. Most customers can use the app without the API. Use the API when you need integration, automation, reporting pipelines, or internal tooling. If you want an AI agent to call iSearchFrom instead of writing code, use the [MCP server](/mcp/overview/) — same capabilities, no HTTP plumbing. This page is orientation — the base URL, auth, and a first request. For the **complete, interactive reference**, open the API explorer: [Interactive API Reference ↗](https://api.isearchfrom.com/docs/api-key)Every endpoint, parameter, request and response schema, and error — generated directly from the live API and browsable in your browser. This is the authoritative contract; start here to build against a specific endpoint. Customer API scope Admin endpoints are intentionally omitted. The reference above shows only the customer-facing, API-key-accessible surface. ## Base URL [Section titled “Base URL”](#base-url) All API requests go to a single base URL: ```text https://api.isearchfrom.com/v1 ``` ## Authentication [Section titled “Authentication”](#authentication) * App requests App-driven requests use browser session cookies and the active organization selected in the app. * Server automation Server-to-server automation should use an API key in the `X-API-Key` header. For API key setup, see [Authentication and API Keys](/reference/authentication-api-keys/). ## Endpoints [Section titled “Endpoints”](#endpoints) Every customer-facing endpoint — Projects, API keys, Direct Search, Search Engine Proxy, the Position and Ad trackers, AI Visibility, Tokens — with its exact methods, parameters, request and response schemas, and errors, is in the **[API Reference](https://api.isearchfrom.com/docs/api-key)**. It’s generated from the live API, so it’s always current. The examples below are just enough to get a first request working. ## Examples [Section titled “Examples”](#examples) These examples assume: ```bash BASE_URL="https://api.isearchfrom.com/v1" ``` * Direct Search ```bash curl -X POST "$BASE_URL/tools/serp/direct-search" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "best rank tracker", "country": "US", "language": "en", "engine": "google" }' ``` * Proxy Search ```bash curl -X POST "$BASE_URL/tools/serp/proxy-search" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "best local seo software", "country": "US", "language": "en", "engine": "google", "device": "desktop", "pages": [1] }' ``` ## Required request context [Section titled “Required request context”](#required-request-context) Most endpoints operate inside the active organization or an explicit `{organizationId}` path parameter. Before building an integration, confirm: * the API key belongs to the organization you want to automate * the key has the smallest required permission set * the target organization has enough token balance and the needed product feature enabled * search requests use the same country, language, engine, device, and SafeSearch rules your app workflow would use ## Response shape [Section titled “Response shape”](#response-shape) List endpoints return a `{ data, pagination }` envelope. Object endpoints return the requested resource. Errors return a stable numeric `code` and a human-readable `message`, plus `statusCode`, `timestamp`, and `path`. For the exact pagination envelope, the real rate limits, and the error codes, see [Errors, Pagination, and Rate Limits](/reference/errors-pagination-rate-limits/). ## Changes [Section titled “Changes”](#changes) ### October 2026: ad counts include every paid placement [Section titled “October 2026: ad counts include every paid placement”](#october-2026-ad-counts-include-every-paid-placement) * **Search Engine Proxy pages** (`adCount`, REST and MCP) now count every paid placement on the page: text ads, sponsored product ads, local service ads, sponsored local pins and ad image tiles. Each placement counts once, so a carousel of ten sponsored products counts ten. Pages also return `adBreakdown` (`total`, `text`, `shopping`, `local`, `other`). Pages stored before this change keep the text-ad count they were stored with. * **Ads Tracker results** include every paid placement, each with an `adType`. Analytics accept `adTypes` to narrow to some kinds (for example text ads only). A placement whose advertiser can’t be identified is stored with the domain `unknown`. * **Page layout**: `adsAboveFirstOrganic` counts placements, and the new `adBlocksAboveFirstOrganic` counts the blocks they sit in. Both, and `blocksAboveFirstOrganic`, are `null` on a page with no organic result. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [API Keys](https://app.isearchfrom.com/settings/api-keys) in the app. Create API keys in the app before building server-side integrations. Use the key only from trusted server code. ## Related docs [Section titled “Related docs”](#related-docs) [Authentication and API Keys](/reference/authentication-api-keys/)Create and use API keys for server-to-server requests. [Errors, Pagination, and Rate Limits](/reference/errors-pagination-rate-limits/)Handle list responses, failures, and request limits. [Search Parameters](/reference/search-parameters/)Understand shared search settings used by API endpoints. [Webhooks](/reference/webhooks/)Receive campaign alert events at your own endpoint. [MCP Server](/mcp/overview/)Let an AI agent call iSearchFrom directly over the Model Context Protocol. # Authentication and API Keys > Create, scope, protect, and revoke API keys. API keys authenticate server-to-server requests to the iSearchFrom API. They belong to a organization and can be scoped to selected permissions. ## Create an API key [Section titled “Create an API key”](#create-an-api-key) 1. Open [API Keys](https://app.isearchfrom.com/settings/api-keys). 2. Confirm the correct organization is active. 3. Choose **Create Key**. 4. Enter a descriptive name. 5. Select required permissions. 6. Generate the key. 7. Store the secret immediately. The full secret is shown only once. ## Use an API key [Section titled “Use an API key”](#use-an-api-key) Send the key in the `X-API-Key` header: ```bash curl "https://api.isearchfrom.com/v1/tools/serp/direct-search" \ -H "X-API-Key: $ISEARCHFROM_API_KEY" ``` ## Permission scoping [Section titled “Permission scoping”](#permission-scoping) Choose the smallest permission set required by the integration. Permission areas can include: * project * search * search history * member * team role * team settings * API key * billing * invitation Actions can include create, read, update, and delete. ## Security rules [Section titled “Security rules”](#security-rules) * Never commit API keys to source control. * Never expose keys in browser/frontend code. * Use separate keys per integration. * Rotate keys when people leave or systems change. * Revoke unused keys. * Store keys in a secret manager. ## Revoke a key [Section titled “Revoke a key”](#revoke-a-key) Revoke a key when: * it may be compromised * the integration is retired * a contractor or vendor no longer needs access * you need to rotate credentials Revoked keys stop authenticating requests. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [API Keys](https://app.isearchfrom.com/settings/api-keys) in the app. Create, scope, rotate, and revoke API keys from this page. New key secrets are shown only once. ## Related docs [Section titled “Related docs”](#related-docs) * [API Overview](/reference/api/) * [Roles and Permissions](/team-account/roles-permissions/) * [Security and 2FA](/team-account/security-2fa/) # Errors, Pagination, and Rate Limits > The exact error response shape, error codes, paginated list format, and the real rate limits the iSearchFrom API enforces. Every API client should handle four things: errors (with stable machine-readable codes), paginated lists, rate limits, and plan/usage limits. This page documents each precisely, with the real shapes and numbers the API uses. ## Error responses [Section titled “Error responses”](#error-responses) Every error returns JSON with a consistent shape and the failing request’s HTTP status code: ```json { "statusCode": 402, "code": 39012, "message": "Insufficient token balance for this action.", "timestamp": "2026-07-07T14:05:00.000Z", "path": "/v1/tools/serp/proxy-search" } ``` | Field | Type | Notes | | ------------ | ------ | --------------------------------------------------------------------------------------- | | `statusCode` | number | The HTTP status (also on the response itself). | | `code` | number | **Stable, machine-readable** application code. Branch on this — the message can change. | | `message` | string | Human-readable explanation. For display and logs; never parse it. | | `timestamp` | string | ISO-8601 time the error was produced. | | `path` | string | The request path, for correlating with your logs. | Validation errors (a malformed body or query parameter) return HTTP `400` with a `message` describing the invalid field. ## Error categories [Section titled “Error categories”](#error-categories) Codes are grouped by domain. The `code` ranges you’ll encounter as an API client: | Range | Domain | Typical statuses | | ------- | ------------------------- | -------------------------- | | `33xxx` | API keys / authentication | `401`, `403`, `404` | | `39xxx` | Entitlements & tokens | `402`, `403`, `404` | | `40xxx` | Billing & subscriptions | `400`, `403`, `404`, `409` | | `41xxx` | Search & tracking tools | `400`, `404`, `409`, `429` | ## Common error codes [Section titled “Common error codes”](#common-error-codes) The codes you’re most likely to handle in an integration: ### Authentication & keys [Section titled “Authentication & keys”](#authentication--keys) | Code | Status | Meaning | What to do | | ------- | ------ | -------------------------------------------------- | ---------------------------------------------- | | `33002` | 401 | API key invalid | Check the `X-API-Key` header and the key value | | `33003` | 403 | API key disabled | Re-enable or create a new key | | `33004` | 403 | API key expired | Create a new key | | `33005` | 403 | Key requests a permission the creator doesn’t hold | Reduce the requested permissions | ### Entitlements & tokens [Section titled “Entitlements & tokens”](#entitlements--tokens) | Code | Status | Meaning | What to do | | ------- | ------ | ---------------------------------------------- | -------------------------------------------------- | | `39005` | 403 | Plan doesn’t include this feature | Upgrade or change the workflow | | `39006` | 403 | Feature usage limit reached (e.g. daily quota) | Wait for reset, upgrade, or reduce usage | | `39007` | 400 | No organization context on the request | Ensure the key/session is bound to an organization | | `39012` | 402 | Insufficient token balance | Top up tokens, then retry | ### Search & tracking tools [Section titled “Search & tracking tools”](#search--tracking-tools) | Code | Status | Meaning | What to do | | ------- | ------ | ----------------------------------------- | -------------------------------------- | | `41001` | 404 | Search not found | Check the id and organization | | `41002` | 404 | Campaign not found | Check the id and organization | | `41003` | 409 | Campaign name already exists | Choose a unique name | | `41008` | 400 | Cannot run an archived campaign | Unarchive first | | `41011` | 409 | A run is already active for this campaign | Wait for it to finish | | `41013` | 429 | Demo rate limit exceeded | Sign in / use an authenticated request | The full list This is a curated subset. The complete, always-current set of endpoints, request/response schemas, and error responses is in the **[interactive API reference](https://api.isearchfrom.com/docs/api-key)**, generated directly from the API. ## Pagination [Section titled “Pagination”](#pagination) List endpoints return a consistent envelope: ```json { "data": [{ "id": "...", "...": "..." }], "pagination": { "total": 128, "hasMore": true, "nextCursor": "eyJzIjoiY3JlYXRlZEF0Iiwi..." } } ``` | Field | Notes | | ----------------------- | ---------------------------------------------------------------------- | | `data` | The page of results. | | `pagination.total` | Total matching records (omitted on a few very large tables for speed). | | `pagination.hasMore` | `true` when more pages exist. | | `pagination.nextCursor` | Opaque cursor for the next page. Absent on the last page. | ### Query parameters [Section titled “Query parameters”](#query-parameters) | Parameter | Default | Notes | | --------- | -------- | --------------------------------------------------------------- | | `limit` | endpoint | Page size, **1–100**. Values outside the range are clamped. | | `cursor` | — | Pass the previous response’s `nextCursor` to get the next page. | | `sort` | endpoint | Field to sort by (allowed fields vary per endpoint). | | `order` | `desc` | `asc` or `desc`. | Paginate by following `nextCursor` until `hasMore` is `false`: ```bash curl "$BASE_URL/tools/serp/proxy-search?limit=50" -H "X-API-Key: $ISEARCHFROM_API_KEY" # then, using nextCursor from the response: curl "$BASE_URL/tools/serp/proxy-search?limit=50&cursor=eyJz...=" -H "X-API-Key: $ISEARCHFROM_API_KEY" ``` Keep sort stable while paging A cursor is tied to the `sort` and `order` it was created with. If you change them mid-pagination, the API rejects the cursor — drop it and start a new page. ## Rate limits [Section titled “Rate limits”](#rate-limits) Limits are enforced per identity (your user or API key). When you exceed one, the API returns HTTP `429` with a `Retry-After` header (seconds) telling you when to try again. | Limit group | Limit | Applies to | | ------------------------ | ---------------- | -------------------------------------------------------- | | **Default** | **600 / minute** | Almost every authenticated endpoint, including the tools | | Authentication | 5 / minute | Login, sign-up, password reset, OTP | | Public (unauthenticated) | 30 / minute | Anonymous reads of public resources | | Email triggers | 10 / hour | Invites and other outbound-email actions | The **600 requests/minute** default (≈10/sec) is generous for normal automation. If an agent or job sustains bursts near the limit, add small delays or backoff. On a `429`, honor `Retry-After` rather than retrying immediately. ## Plan & usage limits [Section titled “Plan & usage limits”](#plan--usage-limits) Separate from rate limits, your plan can cap: * daily direct searches and daily proxy searches * API and MCP access * project count and member count * token allocation and which product features are enabled A request that would exceed a plan limit fails with an **entitlement** error (`39005`/`39006`) or, when you’re out of tokens, `39012`. There are also per-request product guardrails — for example a maximum number of proxy pages per search, or checks per campaign run — that return a `400` when exceeded. ## Retry guidance [Section titled “Retry guidance”](#retry-guidance) Retry only transient failures. Never blindly retry a client error. | Retry | Don’t retry | | ----------------------------- | --------------------------------------------- | | Transient network failures | Invalid parameters (e.g. bad country) — `400` | | Upstream timeouts on our side | Missing permission — `403` | | `429` (after `Retry-After`) | Plan doesn’t include feature — `39005` | | Polling a `pending`/`running` | Insufficient tokens — `39012` | | status | Campaign exceeds a size limit — `400` | ## Related docs [Section titled “Related docs”](#related-docs) * [API Overview](/reference/api/) * [Interactive API reference](https://api.isearchfrom.com/docs/api-key) * [Plans, Tokens, and Limits](/guides/plans-tokens-limits/) * [Authentication and API Keys](/reference/authentication-api-keys/) * [Troubleshooting](/help/troubleshooting/) # Glossary > Definitions for common iSearchFrom terms. ## AI Visibility [Section titled “AI Visibility”](#ai-visibility) The product area that monitors brand and competitor presence in AI-generated responses. ## Campaign [Section titled “Campaign”](#campaign) A saved recurring setup for SERP Position Tracker, Ads Position Tracker, or AI Visibility. ## Check [Section titled “Check”](#check) One unit of work inside a campaign run. A check may represent one keyword, prompt, platform, or location combination. ## Direct Search [Section titled “Direct Search”](#direct-search) A tool that builds a Google or Bing search URL and redirects you to the search engine. ## Map pin [Section titled “Map pin”](#map-pin) An exact latitude/longitude location used for proxy searches. ## Geo-grid [Section titled “Geo-grid”](#geo-grid) A square of coordinates around a business, searched point by point, so Maps rank can be read as coverage across an area rather than a single number. ## Local pack [Section titled “Local pack”](#local-pack) The three local results on an ordinary Google Search page. A different product from the Maps canvas, with its own ranking and its own ad inventory. ## Project [Section titled “Project”](#project) A folder-like grouping for searches and campaigns. ## Proxy search [Section titled “Proxy search”](#proxy-search) A captured search run through Search Engine Proxy. ## Run [Section titled “Run”](#run) One scheduled or manual execution of a campaign. ## Search Engine Proxy [Section titled “Search Engine Proxy”](#search-engine-proxy) A tool that captures search result pages and stores structured results inside iSearchFrom. ## SERP [Section titled “SERP”](#serp) Search engine results page. ## Local Presence Score [Section titled “Local Presence Score”](#local-presence-score) How visible a business is across its whole grid, scored 0–100, where 100 is rank 1 at every active point. Each point earns credit for how high it ranks, decaying to nothing past #20, so a business sitting at #4 everywhere scores differently from one at #50 everywhere. Switched-off points are excluded rather than counted as misses. ## Token [Section titled “Token”](#token) A usage credit for captured searches, recurring checks, and AI Visibility work. ## Organization [Section titled “Organization”](#organization) The team or business account that holds projects, searches, campaigns, members, billing, and API keys. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [app home](https://app.isearchfrom.com/) in the app. Use the app sidebar and organization selector to connect these terms to the product areas you use. # Search Parameters > Reference for country, city, map pin, language, device, domain, SafeSearch, and result page settings. Search parameters control the market and result shape for Direct Search, Search Engine Proxy, SERP Position Tracker, and Ads Position Tracker. ## Query [Section titled “Query”](#query) The search term. Queries must be specific enough to produce useful results. Examples: ```text best local seo software rank tracker by city emergency plumber berlin ``` ## Country [Section titled “Country”](#country) The target country or market. Country is required for most search workflows. ## City [Section titled “City”](#city) City refines the country location. Use city when local intent matters. ## Map pin [Section titled “Map pin”](#map-pin) Map pin uses latitude and longitude for an exact location origin. Latitude and longitude must be provided together. Use map pin for neighborhood-level local research. ## Language [Section titled “Language”](#language) Language controls the search/interface language. It does not always mean results will only include pages in that language unless language-only restriction is enabled. ## Device [Section titled “Device”](#device) Device controls the result type for proxy and tracker workflows. Available device categories include desktop, mobile, and tablet variants. Direct Search does not use device selection. ## Engine [Section titled “Engine”](#engine) Supported engines depend on the tool. Direct Search and Search Engine Proxy support Google and Bing where available. SERP Position Tracker focuses on Google. ## Domain [Section titled “Domain”](#domain) Domain selects the search engine domain where supported, such as a country-specific Google domain. Use this only when the domain itself is part of your research question. ## SafeSearch [Section titled “SafeSearch”](#safesearch) SafeSearch controls search engine filtering. Use the setting that matches your reporting policy or customer scenario. ## Country-only [Section titled “Country-only”](#country-only) Country-only restricts results to pages from the selected country when supported. Use it for scoped market research, not as a default. ## Language-only [Section titled “Language-only”](#language-only) Language-only restricts results to pages in the selected language when supported. Use it when page language matters. ## Pages [Section titled “Pages”](#pages) Search Engine Proxy can request multiple result pages. Start with page 1 and add more only when deeper coverage is needed. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Direct Search](https://app.isearchfrom.com/tools/serp/direct-search) in the app. Use Direct Search for a quick parameter test, or Search Engine Proxy when you need captured results inside iSearchFrom. ## Related docs [Section titled “Related docs”](#related-docs) * [Direct Search](/search-tools/direct-search/) * [Search Engine Proxy](/search-tools/search-engine-proxy/) * [International SERP Checks](/use-cases/international-serp-checks/) # Webhooks > Receive campaign alert events at your own HTTPS endpoint — the exact payload, delivery, and retry behavior. iSearchFrom can `POST` an event to an endpoint you control whenever a tracking campaign raises an alert. This page covers that **campaign alert** webhook. Use it to route alerts into an incident tool, an internal dashboard, or a data warehouse. For Slack, Microsoft Teams, Google Chat, Discord or Telegram, connect the chat directly instead — see [Alert Channels](/tracking/alert-channels/). There are two ways to receive the same payload: * **A campaign’s webhook URL** — set in one campaign’s alert settings. Available on every plan. * **A webhook destination** — saved once under **Settings → Alerts & channels** and used by any number of campaigns through routing. Requests are **signed** and carry an **idempotency key** (see [Verify signatures](#verify-signatures)). Available on plans with chat and webhook destinations. To receive exported data — rankings, ads, AI visibility, search history and more — as signed batches on a schedule, use an [export webhook destination](/exports/webhooks/) instead. Scope Webhooks are **alert deliveries**, not a general event-subscription system. They fire when a campaign raises an alert of any severity and either has a webhook URL configured or routes its alerts to a webhook destination. Which alert types a campaign raises at all is set in its alert settings. There is no subscribe-to-any-event API. ## When to use webhooks [Section titled “When to use webhooks”](#when-to-use-webhooks) * Alerts need to enter an internal tool, Slack channel, or incident workflow. * You want to store alert events in a data warehouse. * Another workflow should run when a campaign’s rankings, ads, or AI visibility change. For a complete walkthrough including a relay to Slack, see the cookbook [Send alerts to Slack](/cookbooks/alert-webhooks-to-slack/). ## Configure a webhook [Section titled “Configure a webhook”](#configure-a-webhook) 1. Open the campaign’s **alert settings** (in the app, or via the campaign’s `alertSettings` through the API). 2. Enable alerts and set a **public HTTPS endpoint** as the webhook URL. 3. Save the campaign. 4. Trigger a low-risk alert to confirm delivery before relying on it. The endpoint must accept `POST` and return a `2xx` quickly. If you need slow processing, acknowledge fast and process asynchronously. ## Payload shape [Section titled “Payload shape”](#payload-shape) Every delivery is JSON with this exact shape: ```json { "event": "tool.alert.created", "timestamp": "2026-07-07T14:05:00.000Z", "data": { "alertId": "…", "targetType": "serp_position_campaigns", "campaignId": "…", "campaignName": "Brand keywords — US", "type": "…", "severity": "…", "metadata": { "…": "…" } } } ``` | Field | Notes | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event` | `tool.alert.created` for alerts (**Send test** posts `notification.test`). Branch on it; new event types may be added. | | `timestamp` | ISO-8601 time the alert was raised; the same on every retry, so it can be older than the delivery. Check freshness with the signature’s `t`, not this. | | `data.alertId` | Unique alert id — use it for **idempotency** (see below). | | `data.targetType` | Which tool’s campaign raised it — see the values below. | | `data.campaignId` | The campaign that raised the alert. | | `data.campaignName` | The campaign’s name, for display. | | `data.type` | The alert rule that fired. | | `data.severity` | The alert’s severity. | | `data.metadata` | Alert-type-specific detail (varies by `type`). | `data.targetType` is one of `serp_position_campaigns` (SERP Position Tracker), `serp_ad_tracker_campaigns` (Ads Position Tracker), `ai_visibility_campaigns` (AI Visibility), `serp_maps_campaigns` (Maps Visibility) and `marketplace_campaigns` (Marketplace). Handle unknown fields safely — new fields may be added without changing existing ones. ## Delivery behavior [Section titled “Delivery behavior”](#delivery-behavior) * **Retries:** a failed delivery is retried with exponential backoff. For a campaign’s webhook URL that is **4 attempts** in all, waiting about 5s, 10s and 20s between them, so an endpoint that is down for longer than about half a minute misses the alert; a `4xx` other than `408` or `429`, or a redirect, is not retried at all. For a webhook destination it is **4 attempts** over several minutes, and a `429` waits for your `Retry-After`. After that it’s given up. * **Destination health:** a webhook destination that answers `404`/`410`/`401`/`403` or redirects three times in a row (spread over about a quarter of an hour), or has five deliveries in a row given up on, is set aside as **Needs attention**; the organization’s owners and the person who set it up get an email and an in-app notice. Other `4xx` answers fail that one alert, and five such failures in a row set the destination aside too. An alert not delivered within 24 hours is dropped. * **Idempotency:** the same alert can arrive more than once (a retry after your endpoint was briefly slow). De-duplicate on `data.alertId` if duplicates would matter. * **Response:** return `2xx` only once you’ve accepted the event. Use `5xx`, `429` or a timeout for temporary failures (which trigger a retry). For a campaign’s own webhook URL, any other `4xx` ends that delivery: it is not retried. A webhook destination that answers `401`, `403`, `404`, `410` or a redirect is retried instead (see Destination health above). * Keep responses small — iSearchFrom does not surface your endpoint’s response anywhere. ## Verify signatures [Section titled “Verify signatures”](#verify-signatures) Requests to a **webhook destination** carry two extra headers: | Header | Value | | ----------------- | ------------------------------------------------------------------------------------------ | | `ISF-Signature` | `t=,v1=.">`, keyed with the signing secret | | `Idempotency-Key` | Stable per alert and destination — the same value on every retry and resend | When support sends a failed delivery again, it carries the same `Idempotency-Key`: a receiver that de-duplicates on it drops the resend as a repeat. The signing secret is shown **once**, when the destination is created. **New signing secret** on the destination replaces it: the old one stops working at once. To verify a request: 1. Split `ISF-Signature` on `,` into `t` and `v1`. 2. Compute HMAC-SHA256 of `` `${t}.${rawBody}` `` with your secret, as hex. Use the raw request body, before any JSON parsing. 3. Compare it with `v1` in constant time, and reject a `t` more than a few minutes old. ```ts import { createHmac, timingSafeEqual } from 'node:crypto'; export function verify(rawBody: string, header: string, secret: string, toleranceS = 300): boolean { const parts = Object.fromEntries(header.split(',').map((part) => part.split('='))); const timestamp = Number(parts.t); if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceS) { return false; } const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex'); const given = Buffer.from(parts.v1 ?? '', 'hex'); const wanted = Buffer.from(expected, 'hex'); return given.length === wanted.length && timingSafeEqual(given, wanted); } ``` **Send test** in the app posts `{"event":"notification.test", …}` — branch on `event` and answer `2xx`. A campaign’s own webhook URL is not signed; put a token in your URL if you need to authenticate it. ## Security recommendations [Section titled “Security recommendations”](#security-recommendations) * Use HTTPS and a **unique URL per integration** so you can rotate or revoke one independently. * The webhook URL is effectively a secret — treat a leaked one as compromised and rotate it. * If your receiver needs to authenticate the caller, put a secret token **in your own URL path/query** or use a gateway rule. Never put API keys in the webhook URL. * Don’t put secrets in campaign names or alert labels — they appear in the payload. ## Related docs [Section titled “Related docs”](#related-docs) * [Alert Channels](/tracking/alert-channels/) * [Send alerts to Slack (cookbook)](/cookbooks/alert-webhooks-to-slack/) * [Schedules and Alerts](/tracking/schedules-alerts/) * [AI Visibility Alerts](/ai-visibility/alerts/) * [Errors, Pagination, and Rate Limits](/reference/errors-pagination-rate-limits/) # Direct Search > Build a localized Google or Bing search URL for fast manual checks. Direct Search is the fastest way to check a search result manually. It builds a search URL with your selected market, language, search engine, city, domain, and SafeSearch settings, then opens the search engine directly. Use it when speed matters Direct Search is best when you need a quick manual answer. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) when you need captured results, parsed result items, HTML preview, map pin targeting, comparison, extra pages, or team evidence inside iSearchFrom. ## Good uses [Section titled “Good uses”](#good-uses) Spot checks Open Google or Bing with consistent market settings. Query validation Test a search term before creating a tracker. Light research Compare market and language behavior quickly. Repeatable manual searches Save useful settings for later reuse. ## Create a direct search [Section titled “Create a direct search”](#create-a-direct-search) ### Open Direct Search [Section titled “Open Direct Search”](#open-direct-search) Open **Direct Search** from the app navigation. ### Enter the search query [Section titled “Enter the search query”](#enter-the-search-query) Use the same wording a customer, buyer, or stakeholder would use. ### Choose search settings [Section titled “Choose search settings”](#choose-search-settings) Select Google or Bing, country, language, and optional city, search domain, and SafeSearch settings. ### Refine scope if needed [Section titled “Refine scope if needed”](#refine-scope-if-needed) Turn on country-only or language-only restrictions only when your workflow requires them. ### Run the search [Section titled “Run the search”](#run-the-search) iSearchFrom creates the destination URL and redirects you to the search engine. ## Search settings [Section titled “Search settings”](#search-settings) * Required \| Setting | What it controls | | --- | --- | | Query | The phrase sent to the search engine | | Engine | Google or Bing | | Country | The target market | | Language | The search or interface language | * Optional \| Setting | What it controls | | --- | --- | | City | City-level location signal when available | | Domain | Search engine domain, such as a country-specific Google domain | | SafeSearch | Search engine filtering behavior | | Country only | Restricts results to pages from the selected country when supported | | Language only | Restricts results to pages in the selected language when supported | Direct Search does not include device selection. Use Search Engine Proxy or tracker workflows when device targeting matters. ## Save a search [Section titled “Save a search”](#save-a-search) Save a direct search when you expect to repeat it. Saved searches are useful for monthly checks, client research, teammate handoff, and creating a repeatable setup before moving to a proxy search or tracker campaign. You can review saved searches in [History and Saved Searches](/search-tools/history-saved-searches/). ## Tips and mistakes [Section titled “Tips and mistakes”](#tips-and-mistakes) Use specific queries `best running shoes` and `best running shoes berlin` can produce very different results. Pick market before city City settings should refine the country, not replace it. Do not expect archived results Use Search Engine Proxy when you need captured evidence inside iSearchFrom. Do not compare from memory Save both searches or use Search Engine Proxy compare mode. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Direct Search](https://app.isearchfrom.com/tools/serp/direct-search) in the app. Use this page for fast localized Google or Bing links that do not need a captured SERP in iSearchFrom. ## Related docs [Section titled “Related docs”](#related-docs) [Search Engine Proxy](/search-tools/search-engine-proxy/)Capture localized result pages and structured SERP data. [History and Saved Searches](/search-tools/history-saved-searches/)Find past searches and reuse saved settings. [Search Parameters](/reference/search-parameters/)Understand country, language, city, domain, device, and SafeSearch settings. # History and Saved Searches > Browse, replay, save, export, and manage past direct and proxy searches. Search History gives your organization a record of past Direct Search and Search Engine Proxy activity. Use it to find old research, repeat useful searches, keep saved presets, and clean up entries you no longer need. ## What appears in history [Section titled “What appears in history”](#what-appears-in-history) History can include: * Direct Search runs * Search Engine Proxy runs * saved searches * query, language, location, domain, engine, and device metadata * created time * user who ran the search when available Your history is scoped to the active organization and available permissions. ## Search and filter history [Section titled “Search and filter history”](#search-and-filter-history) Use the history filter to find searches by query or visible metadata. You can also switch between all history and saved searches. Useful filters include: * query terms * tool type, such as Direct or Proxy * location * engine * saved status ## Save and unsave searches [Section titled “Save and unsave searches”](#save-and-unsave-searches) Save a search when you want it pinned for future use. Unsave it when it was only useful temporarily. Good saved searches are: * repeatable * named or scoped by project * tied to a recurring reporting question * useful to more than one teammate ## Replay a search [Section titled “Replay a search”](#replay-a-search) Open a past search when you want to inspect or rerun it. For Direct Search, this opens the destination URL. For proxy searches, you can return to the captured search details and pages. ## Export history [Section titled “Export history”](#export-history) Use export when you need to move search history into a report, spreadsheet, or client deliverable. Exported fields can include details such as: * source * query * language * location * domain * engine * device * created time * destination URL Plan access can affect export availability. ## Delete old entries [Section titled “Delete old entries”](#delete-old-entries) Delete searches that are no longer useful, especially test runs with confusing settings. Be careful before deleting shared organization research that teammates may depend on. ## Tips [Section titled “Tips”](#tips) * Save the final version of a search after testing settings. * Use projects to keep saved searches organized. * Filter by tool type when comparing Direct Search and proxy research. * Export only the searches needed for a report, not the entire organization history. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [History](https://app.isearchfrom.com/history) in the app. History shows recent searches and saved searches for the active organization. ## Related docs [Section titled “Related docs”](#related-docs) * [Direct Search](/search-tools/direct-search/) * [Search Engine Proxy](/search-tools/search-engine-proxy/) * [Projects](/search-tools/projects/) * [Agency Client Reporting](/use-cases/agency-client-reporting/) # Projects > Organize searches and campaigns by client, market, product, or research goal. Projects keep work organized inside a organization. A project can hold saved searches, proxy research, tracker campaigns, and AI Visibility campaigns. Use projects whenever a search or campaign belongs to a larger goal. ## When to create a project [Section titled “When to create a project”](#when-to-create-a-project) Create a project for: * a client * a website * a country or region * a product launch * a PPC campaign * a local SEO audit * a competitor research effort * an AI visibility monitoring program Examples: * `Acme - US SEO` * `Germany PPC Watchlist` * `Retail Marketplace Checks` * `AI Visibility - Enterprise Product` ## Create a project [Section titled “Create a project”](#create-a-project) 1. Open **Projects**. 2. Select the active organization. 3. Choose **New Project**. 4. Enter a clear name. 5. Add a short description. 6. Save the project. After creation, use the project selector in the app shell or campaign setup to associate work with that project. ## Edit a project [Section titled “Edit a project”](#edit-a-project) Edit the name or description when the scope changes. Keep names short enough to scan in dropdowns, but specific enough that teammates understand the purpose. ## Delete a project [Section titled “Delete a project”](#delete-a-project) Deleting a project can remove organization around saved searches and campaigns. Before deleting, confirm whether the project is still used for reporting or shared team work. ## Project naming tips [Section titled “Project naming tips”](#project-naming-tips) Use a consistent naming convention: | Pattern | Example | | ---------------- | --------------------------- | | Client + market | `Acme - UK SEO` | | Brand + workflow | `Brand PPC Watchlist` | | Product + launch | `Product X Launch Research` | | Channel + market | `AI Visibility - Brazil` | Avoid names like `Test`, `New Project`, or `SEO` unless the organization is very small. ## How projects relate to campaigns [Section titled “How projects relate to campaigns”](#how-projects-relate-to-campaigns) Tracker and AI Visibility campaigns require a project. This keeps recurring work organized and makes it easier to review campaign purpose, usage, and results later. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Projects](https://app.isearchfrom.com/projects) in the app. Create projects before large research or tracking work so searches and campaigns stay grouped. ## Related docs [Section titled “Related docs”](#related-docs) * [Core Concepts](/guides/core-concepts/) * [History and Saved Searches](/search-tools/history-saved-searches/) * [SERP Position Tracker](/tracking/serp-position-tracker/) * [AI Visibility Overview](/ai-visibility/overview/) # Search Engine Proxy > Capture localized search result pages with country, city, device, language, SafeSearch, and map pin controls. Search Engine Proxy runs a captured search and stores the result inside iSearchFrom. Use it when you need evidence, structured SERP data, repeatable settings, or team review. Unlike Direct Search, proxy searches can include device targeting, parsed results, HTML preview, saved result pages, extra result pages, compare mode, and map pin targeting. ## When to use it [Section titled “When to use it”](#when-to-use-it) Capture evidence Save a result page inside iSearchFrom for review or reporting. Review SERP features Inspect organic results, ads, local results, shopping, videos, images, related searches, and AI Overview items when captured. Compare settings Compare country, city, device, SafeSearch, language, and map pin variations side by side. Create a baseline Confirm settings before creating a recurring SERP or ad tracker campaign. ## Run a proxy search [Section titled “Run a proxy search”](#run-a-proxy-search) ### Open Search Engine Proxy [Section titled “Open Search Engine Proxy”](#open-search-engine-proxy) Open **Search Engine Proxy** from the app navigation. ### Enter the search term [Section titled “Enter the search term”](#enter-the-search-term) Use the exact query you want to capture. ### Choose the search context [Section titled “Choose the search context”](#choose-the-search-context) Choose search engine, device, language, country and city, or use a map pin for precise local intent. ### Set advanced options [Section titled “Set advanced options”](#set-advanced-options) Set SafeSearch, search domain, scope refinements, and how many pages to scrape. ### Run and review [Section titled “Run and review”](#run-and-review) The search appears in the results area while iSearchFrom processes it. Results may move through pending, running, completed, or failed states. ## Search controls [Section titled “Search controls”](#search-controls) * Location **Prefer a map pin (coordinates) for local results.** City-name targeting geocodes the city approximately — the same city can resolve to a different point on every run, and even districts of the same city return different results. Coordinates are pin-point and repeatable, and in practice capture richer results (local pack, more ads). Use country + city only when a rough market-level view is enough. When accuracy or run-to-run consistency matters — rank tracking, ad monitoring, local SEO — drop a map pin or supply exact coordinates. If you choose map pin mode, select a pin before running or saving the search. * Device Device selection changes the result shape. Mobile and desktop SERPs can differ in ranking, layout, ads, local packs, and AI features. Use the device your customer is most likely to use. For comparison, run the same search twice with different devices and use compare mode. * Pages The default is one result page. Increase pages only when you need deeper coverage beyond the first result set. Each additional page requires more work and can increase token usage. ## Parsed view and HTML view [Section titled “Parsed view and HTML view”](#parsed-view-and-html-view) Parsed view Use this for structured results such as organic items, ads, local results, related questions, related searches, shopping, videos, images, and AI Overview items. HTML view Use this to inspect the captured page closer to how the SERP appeared. If HTML preview is unavailable Use parsed results and rerun the search if the raw page is required. ## Compare mode [Section titled “Compare mode”](#compare-mode) Compare mode helps you review two searches side by side. For reliable comparison, change one variable at a time: Country vs country Compare national markets with the same query and language. City vs city Compare local intent across metro areas. Desktop vs mobile Check whether layout, ads, and rankings change by device. SafeSearch or language Validate how settings change the captured result page. ## Reading results [Section titled “Reading results”](#reading-results) Look at total result count when available, parsed result count, ad count, organic count, local or places results, AI Overview features, related questions, related searches, page status, and failure messages. If a search fails, check whether the location and engine settings are supported, then retry. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Search Engine Proxy](https://app.isearchfrom.com/tools/serp/proxy-search) in the app. Use this page when you need iSearchFrom to capture and store the result page. ## Related docs [Section titled “Related docs”](#related-docs) [Direct Search](/search-tools/direct-search/)Use quick redirects when you do not need captured evidence. [History and Saved Searches](/search-tools/history-saved-searches/)Reuse saved proxy searches and review past work. [Plans, Tokens, and Limits](/guides/plans-tokens-limits/)Understand token usage before loading more pages or scheduling campaigns. # Project access and spending limits > Restrict a project to specific members, cap what each member can spend, and understand exactly when each rule applies. Two independent controls sit on top of [roles](/team-account/roles-permissions/). A **role** says what someone may do; **project access** says where they may do it; a **spending limit** says how much it may cost. All three apply at once, and the strictest one wins. Both controls on this page are part of advanced access control, which is included in some plans. If your organization is not on one of them you can still see and remove anything already in place — you just cannot add new restrictions. Check **Settings → Billing**. ## Project access [Section titled “Project access”](#project-access) By default every project is open to everyone in the organization. Restricting one is opt-in, per project: 1. Open **Projects**, click the project, then **Edit**. 2. Under **Access**, tick the people who should have it. 3. Save. From that moment the project — and everything inside it: campaigns, runs, results, alerts, saved searches, chat conversations, files — is invisible to everyone else. Not greyed out: it does not appear in lists, and a direct link returns “not found”. Untick everyone to re-open the project to the whole organization. Who is never restricted The Access dialog marks some people **“Sees all projects”** and disables their tickbox. Their role grants `Project: Read`, which means every project in the organization — Owner and Admin hold it by design. To restrict one of them, give them a role without `Project: Read`. The built-in **Member** role does not have it, so a plain member is restricted by assignment out of the box. ### Notes worth knowing [Section titled “Notes worth knowing”](#notes-worth-knowing) * **Re-opening is deliberate.** Unticking everyone re-opens the project, and both apps confirm before doing so. * **Somebody leaving does not re-open it.** If everyone assigned to a project leaves the organization, the project stays restricted with nobody on it — visible only to owners and admins — rather than quietly opening to the whole team. Assign someone, or re-open it on purpose. * **Items outside any project stay visible.** A search that was never filed into a project is organization-wide. * **Public campaigns stay public.** Someone who cannot see the project still opens a publicly shared campaign in it exactly as an outside visitor would — a teammate never sees less than a stranger with the link. * **API keys, webhooks and scheduled runs are not restricted.** They act as the organization, not as a person. A restricted member with an API key can therefore reach project data through the API — scope the key, or do not grant `API key: Create`. * **Leaving the organization removes assignments.** Re-inviting the same person does not restore them; assign them again. ## Spending limits [Section titled “Spending limits”](#spending-limits) A limit caps how many tokens **one member** may spend. It applies to everything that costs tokens — searches, campaign runs, data chat — and is checked before each charge. Set a default for everyone in **Settings → Team → Team info → Spending limits**, and override it for one person in **Settings → Team → Members → Edit limits**. | Value | Meaning | | ---------- | -------------------------------------------------------------------------------- | | *(empty)* | No limit | | `0` | May not spend at all — the way to freeze one person without changing their roles | | any number | That many tokens in the period | An override **replaces** the default completely. Someone with an override of “no limit” is unlimited even when the organization default is 500 — that is how an owner exempts themselves. **Use team default** removes the override. ### The two periods [Section titled “The two periods”](#the-two-periods) * **24 hours** — a rolling window, not a midnight reset. Spending 500 tokens at 23:00 still counts against the limit at 08:00 the next morning. There is no moment when the counter empties, so there is no way to spend twice the limit by straddling midnight. * **30 days** — also rolling. The monthly limit is a trailing 30 days, whatever your plan bills on: tying it to the billing cycle would turn “monthly limit” into “yearly limit” for anyone on an annual plan. Usage counts what that member actually consumed: charges minus refunds. A refunded run gives the headroom back. Token purchases and plan renewals are never counted as spending. What a member sees when they hit a limit The action is refused with a message saying the spending limit was reached — deliberately not “out of tokens”, which would send them to the billing page to buy tokens the organization already has. The fix is for an owner or admin to raise the limit. ### Notes worth knowing [Section titled “Notes worth knowing”](#notes-worth-knowing-1) * **Limits apply to owners too**, if the organization default is set. An owner who does not want one gives themselves an override of “no limit”. * **Scheduled runs are not charged to a person.** They belong to the organization, so they consume organization tokens without touching anyone’s limit. A limit is not a way to stop a schedule — pause or delete the campaign instead. * **API-key usage is not charged to a person** either, for the same reason. * **Data chat is priced after the answer.** A member with a little headroom left can slightly overshoot on one long answer; their next question is refused. The answer is never thrown away after being generated. * **Removing a member deletes their override.** Re-inviting them starts from the organization default. ## Which control do I want? [Section titled “Which control do I want?”](#which-control-do-i-want) | You want to… | Use | | ----------------------------------------------- | ------------------------------------------------------------------------------- | | Stop someone using a tool at all | A [role](/team-account/roles-permissions/) without that tool | | Stop someone deleting things | A role without `Delete` on those subjects | | Keep a client’s work away from most of the team | Project access on that client’s project | | Keep one person’s costs bounded | A spending limit | | Freeze one person’s spending immediately | A spending limit of `0` | | Stop all spending in the organization | Pause campaigns, or remove tool permissions — a per-member limit will not do it | ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) **“I ticked members on a project and nothing changed.”** The people who still see it hold `Project: Read`. See the caution above. **“A member says they are blocked but the organization has plenty of tokens.”** They have hit their own limit. **Settings → Team → Members** shows each person’s usage against their limit for both periods. **“Their limit says ‘Default’ but I set one for them.”** “Default” means no override — the value shown comes from the organization default. Use **Edit limits** to give them their own. **“I cannot set a limit or restrict a project.”** Either your plan does not include advanced access control, or you do not hold `Member: Update` (for limits) or `Project: Update` (for access). Removing an existing restriction always works. # Account Settings > Manage your profile, email, password, notification preferences, and account deletion. Account settings control your personal iSearchFrom profile and sign-in security. Organization settings are separate and affect the team. ## Profile [Section titled “Profile”](#profile) You can manage: * display name * profile image * language preference Profile changes affect how you appear inside the app and team areas. ## Email [Section titled “Email”](#email) Your email is used for sign-in and account notifications. If you change your email, iSearchFrom may send a confirmation link before the change takes effect. ## Password [Section titled “Password”](#password) Password options depend on how you signed up. If your account uses an external provider, password changes may be managed by that provider or require a password-backed sign-in method. ## Notification preferences [Section titled “Notification preferences”](#notification-preferences) Notification preferences cover the emails iSearchFrom sends you: * **Weekly and monthly summaries** recap your organization’s activity. Choose the cadence that matches how often you review it. * **Alert emails** carry campaign alerts, batched into at most one email per hour per organization. Turning them off keeps alerts in the app and on any campaign webhook; it only stops the emails. ## Account deletion [Section titled “Account deletion”](#account-deletion) Account deletion is permanent after the grace or confirmation process completes. It can affect: * API keys * team memberships * search history * subscriptions * organizations where you are the sole owner Before deleting your account, transfer ownership or invite another owner to important organizations. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Account Settings](https://app.isearchfrom.com/settings/account) in the app. Use Account Settings for personal profile, email, password, notification, and account actions. ## Related docs [Section titled “Related docs”](#related-docs) * [Security and 2FA](/team-account/security-2fa/) * [Organizations and teams](/team-account/workspaces-organizations/) * [FAQ](/help/faq/) # Audit Log > Who did what in your organization, and how to filter and export the history. The audit log records every administrative action taken in an organization: who did it, what it touched, when, and from which IP address. It is the record you reach for when a campaign disappears, a member’s role changes, or an API key shows up that nobody remembers creating. ## Who can see it [Section titled “Who can see it”](#who-can-see-it) Owners and admins can open the audit log under **Settings → Audit log**. Members cannot. A custom team role can be given the **Audit log** permission from the role editor. The audit log is part of the Agency plan. Events are recorded for every organization from the start, so upgrading shows the full history immediately rather than an empty page. ## What is recorded [Section titled “What is recorded”](#what-is-recorded) | Area | Actions | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Organization | Created, updated (name, description, logo, spending defaults) | | Members | Joined, left, removed, roles changed, spend limit set or cleared | | Invitations | Sent, accepted, declined, revoked | | Team roles | Created, updated, deleted | | Projects | Created, updated, deleted, access restricted or opened | | Campaigns | Created, updated, paused or resumed, deleted, for every tool | | API keys | Created, updated, deleted | | Integrations | Connected, disconnected | | Data exports | Destination created, updated, paused or resumed, deleted; webhook signing secret rotated; an export started by hand or cancelled; a download requested. Runs a schedule starts are not recorded | | Security | SSO provider added, updated or removed; SSO domain verified; SSO enforcement on or off; two-factor on or off | | Billing | Subscription started, plan changed, canceled, resumed | | Support | An entitlement set or removed; billing details, a subscription or an order changed; a support session started or ended | | Audit log | An export was downloaded | Each row names the person who acted. Actions an AI assistant takes through the MCP connection are recorded under the person who connected it. Actions taken through an API key show the key instead of a person, and actions taken by the platform itself, such as a renewal from the billing provider, show as **System**. Actions our team takes for you show as **Support**: the log records that support acted, not which engineer. When a platform administrator signs in as one of your members during a support session, the start of that session is always recorded, its end is recorded when the administrator ends it, and every action taken during it is marked as taken on their behalf. Team role and API key changes record the permissions granted and removed, so a widened grant is visible as such. Deleting the organization is not recorded, because the record would be deleted with it. ## Filtering and export [Section titled “Filtering and export”](#filtering-and-export) Filter by member, action, target type and date range. The filters live in the page address, so a filtered view can be shared as a link. **Export CSV** downloads the current view, up to 50,000 rows, newest first. A file that hit that cap ends with a line starting `# truncated`; narrow the date range for the rest. Exports require the Exports feature on your plan, and every export is itself recorded in the log. ## How long it is kept [Section titled “How long it is kept”](#how-long-it-is-kept) Entries follow your organization’s [data retention](/team-account/data-retention/) window, but are never deleted sooner than 12 months. ## Related [Section titled “Related”](#related) * [Roles and permissions](/team-account/roles-permissions/) * [Plans, tokens and limits](/guides/plans-tokens-limits/) # Data Retention > How long campaign runs, checks and searches are kept, what happens when your plan changes, and how the nightly clean-up works. Every organization keeps its tool output for a retention window measured in months. Runs, checks, results, AI answers, search history, alerts and Data Chat conversations older than the window are deleted by a nightly clean-up. Campaigns, schedules, settings and billing history are configuration, not output, and are never deleted by retention. ## How long data is kept [Section titled “How long data is kept”](#how-long-data-is-kept) The window comes with your plan and can be extended for your organization on request, up to the platform ceiling. Ask support if your contract needs a longer window. | Plan | Retention | | ------- | --------- | | Starter | 12 months | | Pro | 24 months | | Team | 36 months | | Agency | 60 months | An organization whose plan sets no window keeps 24 months. The window is counted from today, so a campaign that runs daily always shows the last 12, 24, 36 or 60 months of history in the app, with no cut-off inside that range. ## The grace period [Section titled “The grace period”](#the-grace-period) Nothing is deleted the moment it passes the window. The clean-up only removes data that is older than the window **plus 60 days**. That grace means a failed card payment, a lapsed subscription or a plan change made by mistake never costs data: restore the plan inside the grace and everything is still there. ## When your window shrinks [Section titled “When your window shrinks”](#when-your-window-shrinks) If your organization moves to a plan with a shorter window, or the entitlement lapses, the old window stays honoured for 60 days from the change. Organization owners (or admins, if the organization has no owner) are told twice: 1. **Immediately**, that the window changed, with the date from which older data becomes eligible for deletion. 2. **One week before that date**, as a final reminder. Upgrading or restoring the plan before the date keeps everything. After it, data outside the new window is deleted on the next nightly clean-up and cannot be recovered. ## Before the first deletion [Section titled “Before the first deletion”](#before-the-first-deletion) Tool output used to be kept for 24 months. If your window is shorter, the first time the clean-up would delete anything from your organization it treats that as a shrink from 24 months: the 24 months stay honoured for another 60 days, and owners are told the same two times as above. This happens once per organization, whenever its data first ages that far. After that, data ageing out past the window you already have is silent. That is the window working as intended. ## What is deleted [Section titled “What is deleted”](#what-is-deleted) * Position tracker, ad tracker, Maps, AI visibility and Marketplace Visibility runs, with their checks, results, mentions and raw results * Search Engine Proxy and Direct Search history * Campaign alerts * Data Chat conversations that have been idle for the whole window, and individual messages older than the window inside a conversation that is still in use * [Audit Log](/team-account/audit-log/) entries, never sooner than 12 months whatever the window ## What is never deleted by retention [Section titled “What is never deleted by retention”](#what-is-never-deleted-by-retention) * Campaigns and their schedules * Saved searches * Organization settings, members and roles * Billing history, invoices and token transactions * The places a Maps campaign has discovered ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Billing](https://app.isearchfrom.com/billing) in the app to see your plan. Retention notices appear in Notifications and by email to organization owners, or to admins when the organization has no owner. ## Related docs [Section titled “Related docs”](#related-docs) * [Plans, Tokens & Limits](/guides/plans-tokens-limits/) * [Manage Subscription](/billing/manage-subscription/) * [Account Settings](/team-account/account-settings/) # Invite Team Members > Add collaborators to a organization and manage pending invitations. Team members help run searches, review campaigns, manage billing, and maintain reporting workflows inside a shared organization. ## Invite a member [Section titled “Invite a member”](#invite-a-member) 1. Open **Team Settings**. 2. Confirm the correct organization is active. 3. Go to **Invite Members**. 4. Enter the teammate’s email address. 5. Send the invitation. The invited person receives an email invitation. Pending invitations appear in the team settings area. ## Manage invitations [Section titled “Manage invitations”](#manage-invitations) Use the invitations list to review who has been invited. Remove an invitation if it was sent to the wrong address or is no longer needed. ## After a member joins [Section titled “After a member joins”](#after-a-member-joins) Review the member’s role and access. Make sure they can perform the work they need without granting unnecessary permissions. ## Common issues [Section titled “Common issues”](#common-issues) | Issue | What to check | | ------------------------- | ----------------------------------------------------- | | Invite email not received | Ask the invitee to check spam and confirm the address | | Cannot invite | Confirm your role allows member management | | Organization not selected | Set the active organization first | | Plan limit reached | Review plan member limits or upgrade | ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Team Settings](https://app.isearchfrom.com/settings/team) in the app. Owners and authorized members invite teammates from Team Settings for the active organization. ## Related docs [Section titled “Related docs”](#related-docs) * [Roles and Permissions](/team-account/roles-permissions/) * [Organizations and teams](/team-account/workspaces-organizations/) * [Single Sign-On](/team-account/sso/) # Roles and Permissions > How access works inside an organization — the permission model, the built-in roles, building a custom role, and how API keys and MCP connections inherit it. Everything a person can do inside an organization comes from the **roles** they hold. A role is a list of permissions; a member’s access is the **sum of every role they hold**. There are no per-person permission switches — if two people need the same access, they get the same role. ## A permission is an action on a subject [Section titled “A permission is an action on a subject”](#a-permission-is-an-action-on-a-subject) Every permission is one **action** applied to one **subject**: | Action | What it allows | | ---------- | --------------------------------------------------- | | **Read** | See it | | **Create** | Make a new one, or run one | | **Update** | Change an existing one, including pausing/archiving | | **Delete** | Remove it permanently | The subjects are: | Group | Subject | Covers | | ----- | -------------------- | -------------------------------------------------------------------------- | | Tools | **Proxy search** | Running and viewing proxy searches | | Tools | **Direct search** | Running and viewing direct searches | | Tools | **Position tracker** | Position tracking campaigns, runs, results, alerts | | Tools | **Ad tracker** | Ad tracking campaigns, runs, results, alerts | | Tools | **Maps** | Maps campaigns, grids, runs, results | | Tools | **Marketplace** | Marketplace campaigns, runs, results, alerts | | Tools | **AI visibility** | AI visibility campaigns, prompts, runs, results | | Tools | **Data chat** | Asking questions of your own data | | Work | **Project** | Projects themselves — see the note below, it does not mean what you expect | | Work | **Search history** | Saved searches and search activity | | Team | **Member** | People in the organization, their roles and spending limits | | Team | **Invitation** | Sending and withdrawing invitations | | Team | **Team role** | Creating and editing the organization’s own roles | | Team | **Team settings** | Organization name, logo, defaults; Delete removes the organization | | Team | **API key** | Creating and revoking API keys | | Team | **Billing** | Plan, invoices, payment method, token top-ups | | Team | **Export** | Downloading data and managing [export destinations](/exports/overview/) | Each tool is its own subject, so “may use Maps, nothing else” is expressible. Restricting a tool removes it from the sidebar and refuses its endpoints; it does not hide the rest of the product. Read on Project means *every* project `Project: Read` is the one permission that does not mean “may open the projects page”. It means **this person sees every project in the organization**, which switches off [project-level access](/team-account/access-control/) for them entirely. Everyone can open the projects page and work in the projects they can see — that comes from being a member, not from this permission. Leave `Project: Read` **off** for any role that should be limited to assigned projects. Owner and Admin hold it deliberately. ## The built-in roles [Section titled “The built-in roles”](#the-built-in-roles) Every organization starts with three roles it cannot edit or delete: | Role | Access | | ---------- | -------------------------------------------------------------------------------------------------- | | **Owner** | Everything, including billing and deleting the organization | | **Admin** | Everything except deleting the organization; full team management | | **Member** | Runs every tool, creates and edits projects and searches, read-only view of the team, own API keys | The Member seat has two deliberate gaps, and both are the point of the seat: **No `Project: Read`.** Read means “every project in the organization”, so granting it would make project restriction a no-op for the default seat — the Access dialog would appear to work and do nothing. Without it a member still sees every unrestricted project plus the ones they are assigned to. **No Delete, anywhere except their own API keys.** A member could otherwise run a campaign and then delete it, or delete the project it lived in, taking a colleague’s history with it. Creating and editing is the job; removing is an administrative act. They also cannot send invitations — deciding who is in the organization is administration however small it looks. An organization that wants a looser member layers a team-authored role on top; that is what those are for. Keep more than one Owner. The last Owner cannot be demoted or removed, so a single-owner organization is one lost account away from having nobody who can manage billing. ## Building your own role [Section titled “Building your own role”](#building-your-own-role) Owners and admins can create roles for their organization under **Settings → Team → Roles**. Start from a template or tick the matrix yourself, name it, and assign it to people from the Members list. Three starting points are offered: * **Search only** — proxy search and direct search, read-only history, nothing else. * **Read-only** — read on every tool, no creating, no deleting. * **No AI tools** — everything except AI visibility and data chat. A worked example — a member who may run searches but must not delete anything or start a token-consuming campaign: 1. **Settings → Team → Roles → New role**, name it `Search only`. 2. Tick **Read** and **Create** for *Proxy search* and *Direct search*. 3. Tick **Read** for *Search history*. 4. Leave everything else empty — including `Project: Read`. 5. Save, then open **Members**, choose the person, **Edit roles**, tick `Search only` and untick the others. Their sidebar now shows only the two search tools. Deleting a project, archiving a campaign or starting a tracker all return “not allowed”, and the tools they cannot use show only campaigns that have been shared publicly. ### Rules the editor enforces [Section titled “Rules the editor enforces”](#rules-the-editor-enforces) * **You cannot create a role more powerful than you are.** Every permission you tick must be one you hold yourself. An admin cannot mint an owner-level role and assign it to themselves. * **You cannot rewrite or delete a role that grants more than you hold** — including narrowing it. Otherwise an admin could quietly strip an owner by editing the role the owner depends on. * **You cannot reshape someone more powerful than you.** An admin cannot change an owner’s roles or spending limits. * **Built-in roles are read-only**, and roles belong to one organization — you will never see or touch another organization’s roles. * **How many roles you may create** depends on your plan. Deleting one frees a slot. Removing access takes effect within a minute Permissions are cached briefly for speed. A role change reaches every open session within about a minute — usually immediately. Signing the person out and back in applies it at once. ## Denials [Section titled “Denials”](#denials) A permission can be a **denial** instead of a grant — “cannot delete Billing”. A denial always wins over a grant from another role, so it is the way to carve an exception out of an otherwise broad role without cloning it. Denials count as power in reverse: removing one gives access back, so the editor requires you to hold the underlying permission before you can delete or rewrite a role that contains one. ## API keys [Section titled “API keys”](#api-keys) An API key carries its own permission list, and it can never exceed the permissions of the person who created it. A member restricted to proxy search cannot mint a key that runs campaigns. Keys act as the organization, not as a person: project restrictions and per-member spending limits do not apply to them. Treat a key as an organization-wide credential — * one key per integration, never a shared key, * the smallest permission set that does the job, * revoke rather than reuse when an integration is retired. See [Authentication and API keys](/reference/authentication-api-keys/). ## MCP connections [Section titled “MCP connections”](#mcp-connections) An [MCP connection](/mcp/security/) is bounded by both what you approved at connect time **and** your live role permissions, re-checked on every call. Reduce someone’s role and their agent’s access shrinks on the next request, with no re-consent needed. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) **“I removed a tool from their role but they can still see the data.”** Check whether they hold the tool through a *second* role — access is the sum of all roles. The Members list shows every role a person holds. **“I restricted a project and nothing happened.”** The people who still see it hold `Project: Read` through one of their roles — the Access dialog marks them “Sees all projects”. Owners and admins always do. **“They cannot open a tool I did not restrict.”** Tools are also gated by your plan. A tool nobody in the organization can open is a plan question, not a permissions one — check **Settings → Billing**. **“An admin cannot edit a role they can see.”** That role grants something the admin does not hold. An owner has to make the change. # Security and 2FA > Protect your iSearchFrom account with good password, session, and two-factor practices. Security settings help protect your account and organization data. Use two-factor authentication when your account supports it, and keep API keys locked down. ## Two-factor authentication [Section titled “Two-factor authentication”](#two-factor-authentication) Two-factor authentication requires a second factor when signing in with your password. Typical setup: 1. Open **Account Settings**. 2. Go to the security or two-factor section. 3. Enter your current password if requested. 4. Scan the QR code with an authenticator app. 5. Enter the 6-digit code. 6. Save backup codes. Store backup codes in a secure place. Each backup code can be used once if you lose access to your authenticator. ## When 2FA is unavailable [Section titled “When 2FA is unavailable”](#when-2fa-is-unavailable) If your account signs in through an external provider, 2FA may need to be managed through that provider or require a password-backed sign-in method first. ## Password tips [Section titled “Password tips”](#password-tips) * Use a unique password. * Avoid sharing accounts. * Revoke other sessions after changing a password if you suspect compromise. * Use your company’s SSO flow if required. ## API key security [Section titled “API key security”](#api-key-security) API keys can access organization data. Keep them secure: * never expose keys in frontend code * never commit keys to version control * use separate keys per system * revoke unused keys * rotate keys when team members leave ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Account Settings](https://app.isearchfrom.com/settings/account) in the app. Personal sign-in security lives in Account Settings. API key security lives in API Keys. ## Related docs [Section titled “Related docs”](#related-docs) * [Authentication and API Keys](/reference/authentication-api-keys/) * [Single Sign-On](/team-account/sso/) * [Roles and Permissions](/team-account/roles-permissions/) # Single Sign-On > Configure organization SSO with an OIDC or SAML identity provider. Single Sign-On lets teams use an identity provider to manage sign-in for an organization. SSO is useful for companies that need centralized access control. SSO availability depends on your plan. ## How the setup works [Section titled “How the setup works”](#how-the-setup-works) Setup is an exchange in two directions, which is why the form is ordered in two steps: 1. **You give your identity provider some URLs from us** — a callback URL (OIDC) or an ACS URL and metadata URL (SAML). These are shown in the form as soon as you pick a protocol and enter your domain, before you fill anything else in. 2. **Your identity provider gives you some values back** — an issuer URL and client credentials (OIDC), or a sign-in URL and certificate (SAML). Do step 1 first. The URLs in step 1 depend only on your domain and provider ID, so they are ready before you create anything on the provider’s side. For OIDC you only need the **issuer URL** — the base address of your provider. We fetch its OpenID configuration automatically, so you never need to enter the authorization, token or JWKS endpoints by hand, and you should not append `/.well-known/openid-configuration` yourself. ## What you need [Section titled “What you need”](#what-you-need) **OIDC** — domain, provider ID, issuer URL, client ID, client secret. **SAML** — domain, provider ID, issuer URL, sign-in URL, and the IdP signing certificate. ## Add an SSO provider [Section titled “Add an SSO provider”](#add-an-sso-provider) 1. Open **Team Settings** and confirm the correct organization is active. 2. Go to **Single Sign-On** and choose **Add provider**. 3. Pick the protocol and enter your domain. A provider ID is suggested for you. 4. Copy the URLs shown in step 1 into your identity provider. 5. Paste the values your provider gives you into step 2. 6. Choose **Test connection**. Registration stays disabled until it passes, so a provider is never created from details that cannot work. 7. Register the provider, then test a real sign-in before requiring SSO for the domain. For OIDC, pick your provider from the **Identity provider** list and enter just your tenant — the issuer URL is built for you. Choose **Other** to type it in full. For SAML, paste your provider’s **metadata URL** (or the XML itself) and choose **Import metadata**. The sign-in URL and signing certificate are read out of the document and filled in for you. ### Auth0 [Section titled “Auth0”](#auth0) 1. **Applications → Create Application → Regular Web Application.** 2. In **Settings → Allowed Callback URLs**, paste the callback URL from step 1. 3. Copy **Client ID** and **Client Secret** from the same page. 4. For **Auth0 domain**, copy the **Domain** field shown on that same settings page — it includes your region, for example `acme.eu.auth0.com`. Auth0’s **Import from URL** tab does not apply here. It expects a Client ID Metadata Document, which describes a public client with no client secret; enterprise SSO needs a confidential one. Use **Create Manually**. ### Okta [Section titled “Okta”](#okta) 1. **Applications → Create App Integration → OIDC → Web Application.** 2. Paste the callback URL from step 1 into **Sign-in redirect URIs**. 3. Copy **Client ID** and **Client secret**. 4. For **Okta domain**, use your org’s domain — `acme.okta.com`, or `acme.oktapreview.com` for a preview org. If you use a custom authorization server, choose **Other** and enter its issuer in full. ### OneLogin [Section titled “OneLogin”](#onelogin) 1. **Applications → Add App → OpenID Connect (OIDC).** 2. Paste the callback URL from step 1 into **Redirect URIs**. 3. Copy the **Client ID** and **Client Secret** from **SSO**. 4. For **OneLogin subdomain**, use just the subdomain — `acme` for `acme.onelogin.com`. ### PingOne [Section titled “PingOne”](#pingone) 1. **Applications → Add Application → OIDC Web App.** 2. Paste the callback URL from step 1 into **Redirect URIs**. 3. Copy the **Client ID** and **Client Secret**. 4. For **Environment ID**, use the environment’s ID from **Settings → Environment**. ### JumpCloud [Section titled “JumpCloud”](#jumpcloud) 1. **SSO Applications → Add New Application → Custom OIDC App.** 2. Paste the callback URL from step 1 into **Redirect URIs**. 3. Copy the **Client ID** and **Client Secret**. 4. No tenant value is needed — JumpCloud’s issuer is the same for everyone. ### Microsoft Entra ID [Section titled “Microsoft Entra ID”](#microsoft-entra-id) 1. **App registrations → New registration**, and add the callback URL from step 1 as a **Web** redirect URI. 2. Create a secret under **Certificates & secrets** and copy its value — it is only shown once. 3. Client ID is the **Application (client) ID**. 4. For **Directory (tenant) ID**, use the GUID from **Overview**. Do not use `common` — that shared endpoint reports a placeholder issuer and will be rejected. ### SAML providers [Section titled “SAML providers”](#saml-providers) Metadata flows both ways, and using it on both sides means almost nothing has to be typed: * **Into your provider** — most SAML IdPs can import our metadata URL directly instead of you filling fields one by one. Look for “import from URL” or “metadata URL” when creating the application. The same URL is also the **SP Entity ID / Audience** value if your provider asks for it separately. * **Into iSearchFrom** — paste your provider’s metadata URL into the setup form and choose **Import metadata**. We read the sign-in URL and signing certificate from it. Re-importing is also how you rotate an expiring certificate: paste the same metadata URL again and the new certificate replaces the old one. ## Require SSO for a domain [Section titled “Require SSO for a domain”](#require-sso-for-a-domain) After testing, you can require SSO for users on the configured domain. Be careful: if the provider is misconfigured, users may be blocked from signing in. Test a real sign-in first. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) The form reports the specific reason a registration failed. The common ones: | Message | What to do | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | No OpenID configuration found at that issuer URL | Use the provider’s base URL, without `/.well-known/openid-configuration` | | Provider reports a different issuer URL | Copy the exact `issuer` value from your provider’s OpenID configuration | | Could not reach your identity provider | Check the issuer URL is correct and reachable from the public internet | | Issuer URL must be a public https address | Internal or private hosts cannot be reached — expose the provider publicly | | A provider with this Provider ID already exists | Choose a different ID, or delete the existing provider first | | Only an owner or admin can register a provider | Ask a workspace owner or admin to complete the setup | | Redirect fails after sign-in | Confirm the callback URL from step 1 is allowed in the identity provider, character for character | | SSO not available | Confirm your plan includes SSO | ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Team Settings](https://app.isearchfrom.com/settings/team) in the app. Organization SSO settings and domain enforcement belong with team/organization controls. ## Related docs [Section titled “Related docs”](#related-docs) * [Invite Team Members](/team-account/invite-team-members/) * [Security and 2FA](/team-account/security-2fa/) * [Manage Subscription](/billing/manage-subscription/) # Organizations and teams > Understand the organization model used for projects, teams, billing, API keys, and saved data. iSearchFrom uses organizations to keep team data, billing, projects, searches, and campaigns together. Some parts of the app may refer to this as an organization. ## What belongs to a organization [Section titled “What belongs to a organization”](#what-belongs-to-a-organization) A organization contains: * members * projects * direct searches * proxy searches * tracker campaigns * AI Visibility campaigns * token balance * subscription and billing history * API keys * team settings ## Active organization [Section titled “Active organization”](#active-organization) The active organization controls what data you see and where new work is created. Before creating a project, search, campaign, API key, or billing change, confirm the correct organization is active. ## Multiple organizations [Section titled “Multiple organizations”](#multiple-organizations) You may belong to multiple organizations if you work with different teams or clients. Switch organizations when you need to separate data, permissions, billing, or reporting. ## Organization settings [Section titled “Organization settings”](#organization-settings) Organization owners and admins can manage: * organization name and description * logo * members * invitations * SSO providers * billing * API keys * deletion Available actions depend on your role and plan. ## Deleting a organization [Section titled “Deleting a organization”](#deleting-a-organization) Deleting a organization is permanent. It can remove projects, campaigns, search data, members, and related organization data. Active subscriptions may be cancelled automatically. Only delete a organization when you are certain the data is no longer needed. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Organizations](https://app.isearchfrom.com/organizations) in the app. Use Organizations to switch or review organizations, then use Team Settings for organization members and controls. ## Related docs [Section titled “Related docs”](#related-docs) * [Invite Team Members](/team-account/invite-team-members/) * [Roles and Permissions](/team-account/roles-permissions/) * [Manage Subscription](/billing/manage-subscription/) # Ads Position Tracker > Monitor paid search placements and competitor ads over time. Ads Position Tracker monitors sponsored search placements for selected domains and queries. Use it for recurring PPC visibility checks, competitor ad monitoring, and brand protection. ## When to use it [Section titled “When to use it”](#when-to-use-it) Confirm your ads appear Check whether tracked advertiser domains show for target queries. Monitor competitors Detect when competitors start or stop advertising. Review ad creative Capture headlines, descriptions, display URLs, and landing URLs when available. Compare markets Review paid visibility by country, language, device, and search settings. Paid only Use [SERP Position Tracker](/tracking/serp-position-tracker/) for organic rankings. ## Create an ad campaign [Section titled “Create an ad campaign”](#create-an-ad-campaign) ### Choose the campaign context [Section titled “Choose the campaign context”](#choose-the-campaign-context) Open **Ads Position Tracker**, choose **New Ad Campaign**, select a project, and name the campaign. ### Add advertiser domains [Section titled “Add advertiser domains”](#add-advertiser-domains) Add your advertiser domain and competitors that you will act on. ### Add search queries [Section titled “Add search queries”](#add-search-queries) Add paid-search terms you care about. Keep brand monitoring separate from category monitoring when the decisions differ. ### Choose search settings [Section titled “Choose search settings”](#choose-search-settings) Choose country, language, device, engine, and optional city, search domain, SafeSearch, and scope refinements. ### Configure alerts and schedule [Section titled “Configure alerts and schedule”](#configure-alerts-and-schedule) Configure ad alerts, choose a schedule, review estimated token usage, and create the campaign. ## Campaign inputs [Section titled “Campaign inputs”](#campaign-inputs) * Core inputs | Input | Guidance | | --------------- | ------------------------------------------ | | Tracked domains | Add your advertiser domain and competitors | | Queries | Add paid-search terms you care about | | Schedule | Match the pace of PPC decisions | * Search settings | Input | Guidance | | ---------------------------- | ---------------------------------------------------- | | Country and city | Match the ad market | | Language | Match the language used by searchers | | Device | Match expected customer device behavior | | SafeSearch | Use the setting that matches your reporting standard | | Country only / language only | Use for tightly scoped markets | * Limits Campaigns require at least 1 tracked domain and 1 query. A campaign supports up to 10 tracked domains and 10 queries. ## Reading ad results [Section titled “Reading ad results”](#reading-ad-results) Presence Which tracked domains appeared as ads and whether ads started or stopped appearing. Position Best ad position and paid ranking trend. Creative Captured headline, description, display URL, and landing URL. Differences Query-level, market-level, and device-level changes. Ad rank can change frequently. Review trends and repeated appearances instead of overreacting to a single run. ## Alerts [Section titled “Alerts”](#alerts) Ads Position Tracker can alert when a tracked domain starts showing sponsored results or stops showing sponsored results. Best alert candidates Use ad alerts for brand terms, high-value category queries, and competitor watchlists. ## Tips [Section titled “Tips”](#tips) Separate intent Keep brand monitoring separate from category monitoring. Limit competitors Include competitor domains only if you will act on the data. Match scope Use exact country or exact language when ad coverage needs a tightly scoped market. Review landing URLs Landing URLs often reveal campaign intent better than position alone. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Ads Position Tracker](https://app.isearchfrom.com/tools/serp/ad-tracker) in the app. Create and manage paid-search monitoring campaigns from this page. ## Related docs [Section titled “Related docs”](#related-docs) [Schedules and Alerts](/tracking/schedules-alerts/)Configure recurring ad checks and notifications. [Reading Tracker Results](/tracking/reading-tracker-results/)Understand tracker summaries, latest runs, and trend views. [PPC Competitor Monitoring](/use-cases/ppc-competitor-monitoring/)Use ad tracking for competitor and brand-protection workflows. # Alert Channels > Send campaign alerts to Slack, Microsoft Teams, Google Chat, Discord, Telegram or your own webhook, route them per project, and choose which alerts reach you. Alerts always land in the campaign’s alert feed. Beyond that, they can reach people three ways: **email and in-app notices** for members, **chat spaces** your team already reads, and **webhooks** into your own systems. This page covers the second and third, and how each member chooses what reaches them. Channels carry alerts from every alerting tool: SERP Position Tracker, Ads Position Tracker, Maps Visibility, Marketplace and AI Visibility. ## Who receives alerts by email and in the app [Section titled “Who receives alerts by email and in the app”](#who-receives-alerts-by-email-and-in-the-app) * **Owners** receive every alert by default, as before: in-app straight away, by email at most once an hour. They can narrow this under **My alerts** like anyone else. * **Admins and members** opt in under **Settings → Account → My alerts**. Each person picks email and/or in-app, a minimum severity, and optionally which projects. They only ever receive alerts for projects they can access. * Anyone can turn alert emails off entirely in their notification preferences. ## Chat and webhook destinations [Section titled “Chat and webhook destinations”](#chat-and-webhook-destinations) A **destination** is one place alerts are posted: a Slack channel, a Teams channel, a Google Chat space, a Discord channel, a Telegram chat, or a webhook endpoint. Destinations are saved once for the organization under **Settings → Alerts & channels** and then used by any number of campaigns. | Destination | How you connect it | | --------------- | ------------------------------------------------------------------------------------------ | | Slack | Connect your workspace once, then pick a channel. Invite the app to private ones. | | Microsoft Teams | Paste the address of a Teams **Workflows** webhook (“Send webhook alerts to a channel”). | | Google Chat | Paste the incoming-webhook address of a space. | | Discord | Paste the webhook address from the channel’s integrations settings. | | Telegram | Open the link the app gives you and start the bot, in a private chat or a group. | | Webhook | Paste a public HTTPS endpoint. Payloads are signed — see [Webhooks](/reference/webhooks/). | Owners and admins add, change and remove destinations and routes; members can see them. A custom role gets these rights only when they are granted in the [role editor](/team-account/roles-permissions/). Every destination has a **Send test** button. Use it before relying on a new one. A route, and a campaign’s own choice, can each send to up to 10 destinations. Note Telegram channels (broadcast channels) are not supported; use a group. Teams addresses on government or China clouds are not supported. Discord webhooks of forum or media channels are not supported; use a text channel. Install the Slack app on one workspace: organization-wide Enterprise Grid installs are not supported. After a Slack workspace is disconnected and connected again, invite the app to its private channels again. A Slack workspace on the free plan that reaches its message limit is set aside until you resume it. ### How messages arrive [Section titled “How messages arrive”](#how-messages-arrive) * **Chat destinations** collect alerts for about a minute and post them together: one card for a single alert, or a short list of the first five with a count of the rest. A burst of alerts after a run is one message, not twenty. * **Webhooks** get one request per alert, straight away. * In Google Chat, a campaign’s alerts of one day share a thread. * In a Telegram group with topics, alerts go to the **General** topic. To use another topic, open it and send `/start@ ` there instead of tapping the link, with the code from the end of the link the app gives you. * Each destination writes its messages in one language. Leave **Message language** on **Your language** and a new destination uses your account’s language (English if it has none), or pick one. A destination without a language is written in English. ## Where a campaign’s alerts go [Section titled “Where a campaign’s alerts go”](#where-a-campaigns-alerts-go) Alerts follow **routes**: 1. **The campaign’s own choice.** In a campaign’s alert settings, **Send to** can name specific destinations instead of following routing. Choosing none sends that campaign’s alerts nowhere outside the app, email and the campaign’s own webhook URL. If every destination it named has since been deleted, its alerts go nowhere either, rather than falling through to shared channels. 2. **The project’s route** (plans with per-project routing). A project can have a route of its own, with its own destinations and minimum severity. It **replaces** the organization default for campaigns in that project, so one client’s alerts never reach another client’s channel. Switch on **Also send to the default route** to post to both. 3. **The organization default.** Everything else goes to the default route’s destinations. On plans with per-campaign **Send to**, the field shows which destinations routing sends the campaign’s alerts to before you save, marks any that is not receiving right now, and says when the route only passes alerts of a minimum severity. ## When a destination stops working [Section titled “When a destination stops working”](#when-a-destination-stops-working) Temporary failures (timeouts, server errors) are retried automatically, a few times over several minutes. When a receiver asks us to slow down, we wait as long as it asks before trying again; one that keeps asking is treated as failing. A delivery that still fails is given up on and recorded, and an alert that still has not gone out after 24 hours is dropped rather than delivered late. A destination is **set aside** — marked **Needs attention** — when the receiver says it no longer exists or refuses us (a deleted channel, a revoked webhook, a removed bot, a disconnected Slack workspace), or after five deliveries in a row have been given up on (one chat message can carry several alerts). A destination at an address you pasted (webhook, Discord, Google Chat or Teams) answering “not found”, “unauthorized” or a redirect is given three chances in a row first, spread over about a quarter of an hour, since a deploy or a proxy can answer that for a moment. Owners and the person who set the destination up get an email and an in-app notice when that happens. Fix the cause, then **Resume** the destination in **Settings → Alerts & channels**. Saving a new address or channel for it puts it back in service as well; a Slack destination whose workspace was disconnected offers **Reconnect** instead. A destination is marked **Not in your plan** when the organization’s plan no longer includes it, and resumes by itself when the plan allows it again. Both happen when the next alert is routed, so the label can lag a plan change until then. Alerts raised meanwhile are skipped, not queued. Each destination keeps a log of what was sent, kept for 90 days. It is not shown in the app: support reads it when you ask why an alert did not arrive. ## Plans [Section titled “Plans”](#plans) | Feature | Available on | | ----------------------------------------------------------- | ---------------------------------------- | | Email and in-app alerts, and a webhook URL on each campaign | Every plan | | Chat and webhook destinations, and alert routing | Plans that include alert channels | | Per-campaign **Send to** | Plans that include choosing per campaign | | Per-project routing | Plans that include per-project routing | A plan with alert channels also sets which destination types you can add and how many destinations you can save; **Settings → Alerts & channels** shows how many you have left. [Plans and pricing](https://isearchfrom.com/pricing) lists which plans include each feature. The campaign’s own webhook URL keeps working on every plan, in the same format as before. If the plan stops including per-project routing, existing project routes still keep those projects’ alerts out of the shared default route (unless the route also sends to the default route), so no client’s alerts reach another client’s channel. Their chat destinations stop receiving them; email and in-app are unaffected. Delete a project route to send its project’s alerts to the default route again. The same holds for a campaign that chose its own destinations: if the plan stops including that choice, the campaign’s alerts stay out of shared channels. Its alert settings say so; choose **Follow alert routing** there to send them to the routes again. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Alerts & channels](https://app.isearchfrom.com/settings/alerts) in the app to add destinations and routes, or [Account settings](https://app.isearchfrom.com/settings/account) for **My alerts**. ## Related docs [Section titled “Related docs”](#related-docs) * [Schedules and Alerts](/tracking/schedules-alerts/) * [Webhooks](/reference/webhooks/) * [AI Visibility Alerts](/ai-visibility/alerts/) * [Roles and Permissions](/team-account/roles-permissions/) # Maps Visibility > Track where a business ranks on Google Maps across a grid of coordinates. Maps Visibility measures where a business ranks on the Google Maps canvas from many points around it — not one rank, but a map of ranks. Use it for local SEO reporting, service-area coverage, and spotting competitors who are paying for map placement. ## Why a grid [Section titled “Why a grid”](#why-a-grid) A local business does not have “a” Maps ranking. It ranks 1st to someone standing outside its door and 14th to someone two streets away, in the same search, at the same moment. A single number hides that entirely. A campaign searches from a grid of coordinates around your business and records the rank at each one, so you can see how far your visibility actually reaches. Local Presence Score Rank-weighted visibility across the grid, 0–100. Coverage Share of points where you appear at all (top 10). Pack vs map How your Maps rank compares to your one local-pack rank for the same keyword. Sponsored density How much of the local results are paid placements. ## What a run costs [Section titled “What a run costs”](#what-a-run-costs) One token per check. For Maps that is: ```text grid points × keywords (the Maps canvas — one search per point) + keywords (the local pack — one search, it has no coordinates) ``` A 5×5 grid on 2 keywords is `50 + 2 = 52` tokens. It is a **sum, not a product** — the local pack adds one call per keyword, not one per point, so tracking both surfaces costs 1–11% more than the canvas alone and unlocks the pack-vs-map comparison. The campaign form shows the exact figure before you save. ## Setting one up [Section titled “Setting one up”](#setting-one-up) 1. **Name the business exactly as Google Maps shows it**, and add its Google place id when you can. Rankings are matched on the id — businesses get renamed, and matching on name would reset your history. 2. **Drop the pin** on the business, then pick an area size and detail level. More points means a finer picture and a higher cost per run. 3. **Switch off points you don’t need** — water, farmland, anywhere outside your service area. Each active point costs a token per keyword per run, so a trimmed grid is cheaper *and* more honest: switched-off points are excluded from your Local Presence Score rather than counted as misses. 4. **Choose what to track.** Both surfaces are on by default. 5. **Set a schedule** and, if you want them, alerts on entering or leaving the top 3. The centre point is fixed The centre cell sits on your business’s own address and cannot be switched off — every other reading is compared against it. ## Reading the results [Section titled “Reading the results”](#reading-the-results) Each pin shows the rank at that coordinate, coloured from green (top 3) to red. A pin with a dot had paid results at that point. Click any pin to see the full result list for both surfaces side by side, with each business’s rank on the *other* surface next to it. Maps is not the local pack They are different products with different rankings. A business can lead the pack and still be invisible three streets away on the map. That gap is the point of tracking both. ## Reliability [Section titled “Reliability”](#reliability) Each grid point is checked over one of two routes through our network. If one is blocked or returns nothing, the check is retried on the other, so a bad hour on one route rarely costs you a point. The local pack is served over one route only, and pack checks go there automatically. When a point still cannot be read after those retries, it is marked **not checked** rather than guessed at. It draws as a hollow pin on the grid, it is left out of the Local Presence Score, coverage and average rank, and the run reports how many points it lost. A run that read nothing at all has no score and is skipped by the trend line entirely. Not checked is not the same as not found A grey pin means the business genuinely was not in the results at that coordinate, which is a finding. A hollow pin means we could not get an answer there, which is not. Scoring the second as the first would turn an outage on our side into a visibility drop you never had. Tokens are refunded for checks that fail, so an incomplete run is not billed as a complete one. [Schedules & alerts](/tracking/schedules-alerts/)Run campaigns automatically and get told when rankings move. # Reading Tracker Results > Interpret rankings, paid placements, runs, checks, trends, and alert signals. Tracker results are built from campaign runs. Each run contains checks, and each check produces captured results for the configured keyword, market, device, and settings. ## Runs, checks, and results [Section titled “Runs, checks, and results”](#runs-checks-and-results) | Term | Meaning | | -------- | ------------------------------------ | | Campaign | The saved tracking setup | | Run | One scheduled or manual execution | | Check | One keyword or query inside a run | | Result | One captured ranking or ad placement | If a campaign has 5 keywords, one run normally includes checks for those keywords. ## Statuses [Section titled “Statuses”](#statuses) A **run** is the whole scheduled or manual execution. A **check** is one keyword, prompt, or placement inside it. Both show a status, and they do not always match — a run can finish partially even when individual checks failed. ### Check statuses [Section titled “Check statuses”](#check-statuses) * **Pending**: queued, not started yet. * **Running**: in progress. * **Completed**: the check ran and returned results. * **No answer**: the check ran correctly, but the platform returned no content for it. Not an error — see [Token Usage](/billing/token-usage/) for what this means and when it is charged. * **Failed**: the check could not be completed. Tokens for failed checks are refunded. ### Run statuses [Section titled “Run statuses”](#run-statuses) * **Running**: checks are still being processed. * **Completed**: every check finished. * **Partial**: some checks finished and some failed. The results that did come back are still valid — review the failed checks and rerun if the missing ones matter. * **Failed**: the run could not produce usable results. * **Skipped**: the scheduled run did not start because another run — scheduled or manual — was still in progress. Runs are never stacked on top of each other, so no tokens are spent and the next scheduled occurrence proceeds normally. ## Organic ranking metrics [Section titled “Organic ranking metrics”](#organic-ranking-metrics) For SERP Position Tracker: * **Best position** shows the strongest captured rank for a domain. * **Average position** summarizes ranking strength across captured results. * **Top 10** means page-one visibility in many workflows. * **Top 20** means the domain is close enough to monitor but may not be page one. * **Position change** compares current position with a previous period. Lower numbers are better. A move from `8` to `3` is an improvement. ## Paid ad metrics [Section titled “Paid ad metrics”](#paid-ad-metrics) For Ads Position Tracker: * **Best ad position** shows the strongest sponsored placement captured. * **Paid ranking trend** shows how ad placement changes over time. * **Ad creative** can include headline, description, display URL, and landing URL. * **Ad started** means a tracked domain appeared in sponsored results. * **Ad stopped** means a tracked domain no longer appeared where it previously did. Ad results can be volatile. Look for repeated patterns before making budget decisions. ## Comparing markets [Section titled “Comparing markets”](#comparing-markets) Do not compare rankings across markets unless settings are intentionally aligned. Country, city, language, device, domain, and SafeSearch can all change the result. For cleaner comparison: 1. Use one project. 2. Create one campaign per market. 3. Keep keywords and tracked domains consistent. 4. Use the same schedule. 5. Compare trends over the same date range. ## What to do with changes [Section titled “What to do with changes”](#what-to-do-with-changes) | Signal | Possible action | | --------------------- | ------------------------------------------------------------------------ | | Domain entered top 10 | Review the landing page and protect the gain | | Domain left top 10 | Inspect recent content, technical, and competitor changes | | Competitor moved up | Review their page, ads, and content angle | | Ad started | Check whether it is your ad, competitor ad, or partner/reseller activity | | Ad stopped | Confirm whether the campaign paused intentionally | | Repeated failures | Check settings and rerun a smaller campaign | ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [SERP Position Tracker](https://app.isearchfrom.com/tools/serp/position-tracker) in the app. Open a campaign detail page to review runs, checks, ranking history, and keyword-level results. ## Related docs [Section titled “Related docs”](#related-docs) * [SERP Position Tracker](/tracking/serp-position-tracker/) * [Ads Position Tracker](/tracking/ads-position-tracker/) * [Schedules and Alerts](/tracking/schedules-alerts/) # Schedules and Alerts > Configure recurring tracker and AI Visibility runs without creating noisy campaigns. Schedules decide when campaigns run. Alerts decide which changes should be brought to your attention. Schedules and alerts are available in tracking tools and AI Visibility workflows. ## Schedule frequency [Section titled “Schedule frequency”](#schedule-frequency) Campaigns can run on a recurring schedule. Common options include: * daily * weekly * monthly Use the lowest frequency that still supports the decision you need to make. | Workflow | Recommended starting frequency | | ------------------------------- | ------------------------------ | | New SEO campaign validation | Weekly | | High-priority rank monitoring | Daily or weekly | | PPC brand protection | Daily | | Competitor ad monitoring | Weekly | | AI Visibility market monitoring | Weekly | | Monthly client reporting | Monthly or weekly | ## Time and timezone [Section titled “Time and timezone”](#time-and-timezone) Schedules include a time and timezone. Choose the timezone used by the team or market reviewing the data. If a organization serves multiple regions, create separate campaigns by region instead of relying on one ambiguous schedule. ## Active and paused campaigns [Section titled “Active and paused campaigns”](#active-and-paused-campaigns) Only active campaigns run on schedule. Paused campaigns keep their setup and past results but skip scheduled runs. Pause a campaign when: * the client engagement ended * the market is no longer being tracked * the campaign is being revised * token usage should stop temporarily ## Manual runs [Section titled “Manual runs”](#manual-runs) A manual run executes the campaign immediately. Use manual runs to: * test a new campaign * refresh data before a meeting * validate changed settings * investigate an unexpected movement Manual runs can spend tokens. Review estimated usage before running large campaigns. ## How alerts are evaluated [Section titled “How alerts are evaluated”](#how-alerts-are-evaluated) Every alert compares two windows of runs: the three most recent runs against the three before them. A campaign therefore needs at least four completed runs before it can raise anything, and one bad run never fires an alert on its own. The evaluation is built to stay quiet when the data is not a reading: * A window where most checks failed is skipped entirely. A bad afternoon on our network is not a keyword that stopped ranking or an ad that stopped showing. * Position alerts score a keyword the domain does not rank for at all as position 101, one past the top-100 depth. Losing a ranking is a fall, not a keyword that quietly stops counting. Only keywords checked in both windows are compared, so a keyword you just added cannot trigger a drop. * “Entered the top 10” and “left the top 10” are about whether the domain is on page one for any tracked keyword, not about the average. * AI Visibility swings must be both material and larger than sampling noise at the campaign’s size. A small campaign alerts only on dramatic moves; a large one on modest ones. * Competitor ad alerts fire when a competitor starts advertising, not for every run in which an existing advertiser is still there. Once an alert has been raised, the same event is not raised again as the windows slide past it. The same kind of alert fires again only for a later pair of windows that shows the change on its own, for example a ranking that keeps falling week after week, or when its severity escalates. ## Where alerts go [Section titled “Where alerts go”](#where-alerts-go) * **In the app**: every alert appears in the campaign’s alert feed and, for organization owners, in notifications, the moment it is raised. Admins and members opt in under **My alerts** in their account settings. * **Webhook**: if the campaign has a webhook URL, every alert is posted to it immediately. * **Email**: alerts are batched per recipient. Owners, and members who opted in, receive at most one alert email per hour, carrying every alert raised across all campaigns since the last one, grouped by campaign. A lone alert can therefore take up to an hour to reach the inbox. Each person can turn alert emails off in their notification preferences, and each campaign can be kept out of the email with its “Email alerts” setting without affecting the in-app feed or the webhook. * **Chat and webhook destinations**: on plans that include them, alerts are posted to Slack, Microsoft Teams, Google Chat, Discord, Telegram or signed webhooks, following the organization’s routes or the campaign’s own **Send to** choice. See [Alert Channels](/tracking/alert-channels/). ## Alert types [Section titled “Alert types”](#alert-types) ### SERP Position Tracker [Section titled “SERP Position Tracker”](#serp-position-tracker) Position alerts can detect: * entered top 10 * left top 10 * position changes ### Ads Position Tracker [Section titled “Ads Position Tracker”](#ads-position-tracker) Ad alerts can detect: * ad started * ad stopped ### Maps Visibility [Section titled “Maps Visibility”](#maps-visibility) Maps alerts can detect: * reached the top 3 somewhere on the grid * dropped out of the top 3 across the whole grid * Local Presence Score moved by 10 points or more ### AI Visibility [Section titled “AI Visibility”](#ai-visibility) AI Visibility alerts can detect: * visibility appeared * visibility lost * visibility rose * visibility fell * new sponsored placement * sponsored placement stopped * competitor sponsored activity ### Marketplace [Section titled “Marketplace”](#marketplace) Marketplace alerts can detect: * entered the top 10 on a storefront * left the top 10 * rank changed by 3 places or more * a price change of 10% or more, yours or a competitor’s See [Marketplace Alerts](/marketplace/alerts/) for what deliberately does not fire. ## Avoid noisy alerts [Section titled “Avoid noisy alerts”](#avoid-noisy-alerts) Good alerts are tied to action. Before enabling an alert, ask: * Who receives it? * What decision will it trigger? * Is this change meaningful enough to interrupt someone? * Would a weekly review be better than immediate notification? ## Webhook alerts [Section titled “Webhook alerts”](#webhook-alerts) Some workflows can send alert payloads to a webhook URL. Use webhooks for automation, dashboards, Slack-style workflows, or internal incident/reporting systems. Webhook URLs must be public HTTP endpoints that can accept POST requests. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [SERP Position Tracker](https://app.isearchfrom.com/tools/serp/position-tracker) in the app. Open the tracker or AI Visibility campaign, then edit its schedule and alert settings. ## Related docs [Section titled “Related docs”](#related-docs) * [Alert Channels](/tracking/alert-channels/) * [SERP Position Tracker](/tracking/serp-position-tracker/) * [Ads Position Tracker](/tracking/ads-position-tracker/) * [AI Visibility Alerts](/ai-visibility/alerts/) * [Webhooks](/reference/webhooks/) # SERP Position Tracker > Monitor organic keyword rankings for tracked domains across markets and devices. SERP Position Tracker monitors where selected domains rank in organic search results for selected keywords. Use it when you need recurring visibility data instead of one-time SERP research. ## When to use it [Section titled “When to use it”](#when-to-use-it) Monitor ranking movement Track your domain’s positions over time. Compare competitors See which tracked domains lead across keywords. Track market differences Compare country, city, language, and device behavior. Report visibility changes Detect when a domain enters or leaves page-one rankings. Organic only Use [Ads Position Tracker](/tracking/ads-position-tracker/) for paid ads. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) for one-time captured SERP research. ## Create a campaign [Section titled “Create a campaign”](#create-a-campaign) ### Choose the campaign context [Section titled “Choose the campaign context”](#choose-the-campaign-context) Open **SERP Position Tracker**, choose **New Campaign**, select a project, and name the campaign. ### Add tracked domains [Section titled “Add tracked domains”](#add-tracked-domains) Add your domain and important competitors. Track only domains you will actually review. ### Add keywords [Section titled “Add keywords”](#add-keywords) Add focused search terms, one per line. Group keywords by intent so reports stay readable. ### Choose search settings [Section titled “Choose search settings”](#choose-search-settings) Choose country, language, device, and optional city or search domain settings. ### Configure alerts and schedule [Section titled “Configure alerts and schedule”](#configure-alerts-and-schedule) Choose the alerts you would act on, pick a schedule, review estimated token usage, and create the campaign. ## Campaign inputs [Section titled “Campaign inputs”](#campaign-inputs) * Required | Input | Guidance | | --------------- | ----------------------------------------------------------------------- | | Project | Choose the client, market, or research project this campaign belongs to | | Name | Use a descriptive name such as `UK Brand Keywords` | | Tracked domains | Add your domain and important competitors | | Keywords | Add focused search terms, one per line | * Search settings | Input | Guidance | | ---------------- | ------------------------------------------------------ | | Country and city | Match the market you care about | | Language | Match the search language your customers use | | Device | Pick desktop, mobile, or tablet based on user behavior | | Search domain | Use only when a specific search engine domain matters | * Limits Campaigns require at least 1 tracked domain and 1 keyword. A campaign supports up to 10 tracked domains and 10 keywords. ## Reading results [Section titled “Reading results”](#reading-results) Best position The best captured rank for a tracked domain. Lower rank numbers are better. Average position The average captured rank across results. Top 10 / Top 20 How many results are in common reporting bands. Domain leaders Which tracked domains are strongest across the campaign. Keyword leaderboard Which domains lead for each keyword. Ranking trend How average position changes over time. Position `1` is stronger than position `8`. ## Alerts [Section titled “Alerts”](#alerts) Position Tracker can alert when a tracked domain enters the top 10, leaves the top 10, or has a material position change. Keep alerts actionable Use alerts for movement you would act on. Too many alerts can make the campaign noisy. ## Tips [Section titled “Tips”](#tips) Group keywords by intent Do not mix brand, category, and competitor terms in one campaign unless the report needs them together. Separate markets Use separate campaigns for separate countries when markets behave differently. Verify first Run a Search Engine Proxy check first if you are unsure about language or device settings. Pause stale campaigns Pause campaigns that no longer feed a decision or report. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [SERP Position Tracker](https://app.isearchfrom.com/tools/serp/position-tracker) in the app. Create and manage organic ranking campaigns from this page. ## Related docs [Section titled “Related docs”](#related-docs) [Reading Tracker Results](/tracking/reading-tracker-results/)Understand ranking rows, trends, and summaries. [Schedules and Alerts](/tracking/schedules-alerts/)Configure recurring checks and actionable notifications. [Local SEO Research](/use-cases/local-seo/)Use localized search evidence for market and client reporting. # Agency Client Reporting > Use projects, saved searches, trackers, and exports to support client reporting. Agencies can use iSearchFrom to organize client research, capture evidence, track movement, and prepare recurring reports. ## Recommended organization structure [Section titled “Recommended organization structure”](#recommended-organization-structure) Use one organization for the agency team, then create projects by client and market. Examples: ```text Client A - US SEO Client A - PPC Watchlist Client B - Germany Launch Client C - AI Visibility ``` ## Reporting workflow [Section titled “Reporting workflow”](#reporting-workflow) 1. Create or select the client project. 2. Run Search Engine Proxy checks for important queries. 3. Save searches that should be referenced later. 4. Create tracker campaigns for recurring reporting. 5. Schedule campaigns weekly or monthly. 6. Export relevant history or results when preparing reports. 7. Use comments in your own reporting tool to explain business context. ## What to include in reports [Section titled “What to include in reports”](#what-to-include-in-reports) * market and language settings * device setting * date range * tracked domains * keyword list * ranking movement * paid ad changes * screenshots or captured SERP evidence where appropriate * AI Visibility share of voice and prompt examples when relevant ## Tips [Section titled “Tips”](#tips) * Keep each client in clearly named projects. * Avoid reusing one tracker campaign for unrelated markets. * Save only final research searches, not every test. * Use alerts internally, then summarize for clients. * Review token usage before onboarding large client batches. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Projects](https://app.isearchfrom.com/projects) in the app. Create one project per client or market, then add searches and campaigns under that project. ## Related docs [Section titled “Related docs”](#related-docs) * [Projects](/search-tools/projects/) * [History and Saved Searches](/search-tools/history-saved-searches/) * [SERP Position Tracker](/tracking/serp-position-tracker/) * [Invoices and Payments](/billing/invoices-payments/) # AI Brand Visibility Monitoring > Monitor how AI platforms mention, recommend, and compare your brand. AI brand visibility monitoring helps you understand whether AI platforms include your brand when customers ask category, comparison, and recommendation questions. ## Recommended workflow [Section titled “Recommended workflow”](#recommended-workflow) 1. Create a project for the brand or product line. 2. Create an [AI Visibility campaign](/ai-visibility/create-campaign/). 3. Add your primary domain and brand names. 4. Add direct competitors. 5. Add high-intent prompts. 6. Choose target countries and platforms. 7. Start with a weekly schedule. 8. Review visibility rate, share of voice, recommendations, and raw responses. 9. Use attribute gaps to guide content and positioning. ## Prompt examples [Section titled “Prompt examples”](#prompt-examples) ```text Best tools for local SEO reporting. Compare Acme and ExampleCo for agencies. What software helps monitor Google rankings by country? Best alternatives to ExampleCo for search visibility tracking. ``` ## What to monitor [Section titled “What to monitor”](#what-to-monitor) * whether your brand is mentioned * whether your brand is recommended * competitor share of voice * prompts where competitors win * location or platform differences * sentiment * sponsored mentions * missing attributes ## Tips [Section titled “Tips”](#tips) * Start with fewer prompts and inspect raw responses. * Keep competitor lists realistic. * Split campaigns by product line if buyer intent differs. * Do not treat one raw response as a full market conclusion. * Look for repeated patterns across runs. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [AI Visibility](https://app.isearchfrom.com/tools/ai/visibility) in the app. Create one focused campaign per brand, market, or product line before expanding prompts and platforms. ## Related docs [Section titled “Related docs”](#related-docs) * [AI Visibility Overview](/ai-visibility/overview/) * [Prompts, Brands, and Competitors](/ai-visibility/prompts-brands-competitors/) * [AI Visibility Metrics and Results](/ai-visibility/metrics-results/) # International SERP Checks > Compare search results across countries, languages, devices, and search domains. International SERP checks help you understand how search results differ between markets. Use them for international SEO, localization, expansion planning, and competitor research. ## Recommended workflow [Section titled “Recommended workflow”](#recommended-workflow) 1. Create a project for the market or expansion initiative. 2. Run a [Search Engine Proxy](/search-tools/search-engine-proxy/) search in the first country. 3. Duplicate or recreate the search for another country. 4. Keep the query and device consistent. 5. Change one setting at a time: country, language, city, or domain. 6. Use compare mode to review differences. 7. Save important searches. ## Country and language [Section titled “Country and language”](#country-and-language) Country and language are different settings. * Country controls the target market. * Language controls the search/interface language. For example, a Spanish-language search in the United States can differ from a Spanish-language search in Spain. ## Country-only and language-only [Section titled “Country-only and language-only”](#country-only-and-language-only) Use country-only when you want pages from a specific country. Use language-only when you want pages in a specific language. Do not turn these on by default. They are useful for scoped research, but they can hide broader market behavior. ## What to compare [Section titled “What to compare”](#what-to-compare) * top-ranking domains * local competitors * ads and shopping results * SERP features * AI Overview presence * language-specific content * search intent differences ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Search Engine Proxy](https://app.isearchfrom.com/tools/serp/proxy-search) in the app. Use proxy searches for captured market comparisons, or Direct Search for quick manual checks. ## Related docs [Section titled “Related docs”](#related-docs) * [Search Engine Proxy](/search-tools/search-engine-proxy/) * [Direct Search](/search-tools/direct-search/) * [Search Parameters](/reference/search-parameters/) # Local SEO Research > Check and monitor search visibility for city-level and local-intent keywords. Local SEO work depends on what searchers see in a specific market. iSearchFrom helps you test city-level results, inspect local SERPs, and monitor rankings over time. ## Recommended workflow [Section titled “Recommended workflow”](#recommended-workflow) 1. Create a project for the client or market. 2. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) to inspect important keywords. 3. Use country and city targeting, or a map pin for exact local checks. 4. Compare desktop and mobile if mobile search is important. 5. Save useful searches. 6. Create a [SERP Position Tracker](/tracking/serp-position-tracker/) campaign for recurring monitoring. ## Example project setup [Section titled “Example project setup”](#example-project-setup) ```text Project: Local SEO - Austin Tracked domains: - client.com - competitor-a.com - competitor-b.com Keywords: - emergency plumber austin - best plumber near me - drain cleaning austin ``` ## What to look for [Section titled “What to look for”](#what-to-look-for) * local pack or places results * organic ranking changes * competitors appearing above the client * city-specific landing pages * mobile vs desktop differences * AI Overview presence * related searches that suggest content opportunities ## Tips [Section titled “Tips”](#tips) * Prefer map pin (coordinates) targeting for anything you re-run or track: city names geocode approximately and can land on a different point each run, while a pin gives repeatable, pin-point results — even districts of the same city can return different SERPs. * Use map pin targeting when neighborhood-level differences matter. * Keep one campaign per city if each market has different competitors. * Use weekly tracking for normal monitoring and manual runs before reporting. * Save Search Engine Proxy results that show important SERP layout changes. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Search Engine Proxy](https://app.isearchfrom.com/tools/serp/proxy-search) in the app. Use city or map-pin targeting for local checks, then move repeat monitoring into SERP Position Tracker. ## Related docs [Section titled “Related docs”](#related-docs) * [Search Engine Proxy](/search-tools/search-engine-proxy/) * [SERP Position Tracker](/tracking/serp-position-tracker/) * [Search Parameters](/reference/search-parameters/) # Marketplace and Retail Search Checks > Research how product and retail queries appear across markets and devices. Retail and marketplace teams can use iSearchFrom two ways: to watch where products rank **on the marketplaces themselves**, and to inspect the Google results that surround them. ## Which tool [Section titled “Which tool”](#which-tool) | Question | Tool | | ----------------------------------------------------------------- | --------------------------------------------------------- | | Where do my products rank on Amazon, Google Shopping or Walmart? | [Marketplace Visibility](/marketplace/overview/) | | How many ads sit above my listing, and what is my price standing? | [Marketplace Visibility](/marketplace/overview/) | | What does the Google SERP look like for a product query? | [Search Engine Proxy](/search-tools/search-engine-proxy/) | | Who is bidding on my category in Google Ads? | [Ads Position Tracker](/tracking/ads-position-tracker/) | ## Recommended workflow [Section titled “Recommended workflow”](#recommended-workflow) 1. Create a project for the category or market. 2. Create a [Marketplace Visibility](/marketplace/create-campaign/) campaign covering the shelves you sell on — several at once, since rank on one says little without the others. 3. Track the products by id where you can, and by brand everywhere, so variants and bundles are caught too. 4. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) for the surrounding Google results. 5. Compare mobile and desktop where shoppers differ. 6. Save searches that support merchandising or content decisions. ## Useful query types [Section titled “Useful query types”](#useful-query-types) * product category queries * brand + product queries * competitor product queries * local availability queries * price-sensitive queries * comparison queries ## What to look for [Section titled “What to look for”](#what-to-look-for) * marketplaces ranking above retailers * shopping modules or product carousels * competitor ads * image or video-heavy SERPs * local pack results * AI Overview content * related searches that suggest demand ## Tips [Section titled “Tips”](#tips) * Use mobile checks when customers commonly shop on phones. * Use country and language settings together for international retail. * Track competitor ads separately from organic rankings. * Save representative SERPs before making merchandising recommendations. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Marketplace Visibility](https://app.isearchfrom.com/tools/ecommerce/marketplace) to track shelf position directly, or [Search Engine Proxy](https://app.isearchfrom.com/tools/serp/proxy-search) to capture the surrounding SERP evidence. ## Related docs [Section titled “Related docs”](#related-docs) * [Search Engine Proxy](/search-tools/search-engine-proxy/) * [Ads Position Tracker](/tracking/ads-position-tracker/) * [SERP Position Tracker](/tracking/serp-position-tracker/) # PPC Competitor Monitoring > Track competitor sponsored placements for important search queries. PPC competitor monitoring helps you see when competitors appear in sponsored results, which messages they use, and whether your own ads are visible in target markets. ## Recommended workflow [Section titled “Recommended workflow”](#recommended-workflow) 1. Create a project for the campaign or market. 2. Use [Search Engine Proxy](/search-tools/search-engine-proxy/) to inspect priority queries manually. 3. Identify competitor advertiser domains. 4. Create an [Ads Position Tracker](/tracking/ads-position-tracker/) campaign. 5. Add brand, category, and competitor queries in separate campaigns when possible. 6. Enable ad started and ad stopped alerts for high-value terms. 7. Review paid ranking trend and ad creatives. ## Campaign structure [Section titled “Campaign structure”](#campaign-structure) Use separate campaigns for: * brand terms * category terms * competitor terms * market-specific terms This keeps alerting and reporting easier to understand. ## What to monitor [Section titled “What to monitor”](#what-to-monitor) * whether your domain appears * competitor domains appearing as ads * best ad position * ad headline and description * landing URL * ad started or stopped events * differences by device and country ## Tips [Section titled “Tips”](#tips) * Track only competitors you will act on. * Review landing URLs for offer and positioning changes. * Use daily alerts for brand protection and weekly tracking for broad competitor watchlists. * Do not mix too many unrelated queries in one campaign. ## Open in iSearchFrom [Section titled “Open in iSearchFrom”](#open-in-isearchfrom) Open [Ads Position Tracker](https://app.isearchfrom.com/tools/serp/ad-tracker) in the app. Use this tracker for recurring paid-placement monitoring and competitor ad changes. ## Related docs [Section titled “Related docs”](#related-docs) * [Ads Position Tracker](/tracking/ads-position-tracker/) * [Schedules and Alerts](/tracking/schedules-alerts/) * [Search Engine Proxy](/search-tools/search-engine-proxy/)