---
title: "Create GMB Discovery signal"
method: POST
path: "/api/v1/signals/gmb-listing"
tags: ["Signals"]
---

# Create GMB Discovery signal

`POST /api/v1/signals/gmb-listing`

## Request

- Base URL: `https://api.rodz.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.rodz.io/api/v1/signals/gmb-listing`
- Auth: HTTP bearer

Catch local businesses the moment they appear on Google Maps — newly indexed restaurants, plumbers, salons, and other categories across France or Belgium. Every new business matching your filters is pushed to your webhook.

**Input:** country (FR or BE), category / subcategory (e.g., "Restaurant" or "Italian restaurant"), cities by name (e.g., "Paris", "Lyon"), postal codes (France), rating and review-count ranges, presence filters (hasPhone, hasEmail, hasWebsite, hasSiret), and APE/NAF codes (France-only). Optionally enable keepOnlyConfirmedNew (France-only) to cross-check the French SIRENE registry and drop false positives that were registered before a freshness cutoff (default 180 days, configurable); this switches per-lead pricing from 1 to 5 credits.

**Output:** each newly-indexed business matching your filters is sent as a lead to your webhookUrl containing: company name, website URL, Google Maps URL, category, Google rating, review count, location, SIREN/VAT identifiers, and a generic business email if one is found on the business's website. This is a company-only signal (no person contacts). Optionally enrich each lead with email enrichment, phone enrichment, full SIRENE company data, detected technologies, or homepage content via getEmails/getPhones/getSireneData/detectTechnologies/getHomepageContent flags.

**Use cases:**
- Local-SMB outreach — reach new local businesses (agencies, SMB SaaS, suppliers) as they open.
- Territory-based field sales — filter by city and category to feed a local rep's pipeline.

## Request body

- object: Create a GMB Discovery signal.
  - `name` string, required: Name for this signal
  - `webhookUrl` string, nullable: Where each lead is POSTed as it is produced. Optional (since 2026-09-23): omit it when you read leads with GET /api/v1/signals/{signalId}/leads or route them to a connected CRM or sequencer with `destinations` on PATCH /api/v1/signals/{signalId}.
  - `config` GmbListingConfig, required: Config for gmb_listing (GMB Discovery — newly indexed Google Maps businesses). Multi-country, but the optional ``keep_only_confirmed_new`` freshness filter relies on the French SIRENE registry, so it's only available for FR. Other countries serve from the mirror without the SIRENE cross-check.
    - `getEmails` boolean: Enable email enrichment (0.5 credit/lead, charged only on success)
    - `getPhones` boolean: Enable phone enrichment (5 credits/lead, charged only on success)
    - `getSireneData` boolean: Enrich with French SIRENE registry data (1 credit/lead, charged only on success)
    - `detectTechnologies` boolean: Detect technologies used on the company's public website (1 credit/lead, charged only on success)
    - `getHomepageContent` boolean: Fetch the company's homepage content (1 credit/lead, charged only on success)
    - `country` string: ISO 2-letter country code. 53 countries are available — the largest are US, FR, DE, AU, GB, IT and ES.
    - `category` string, nullable: Google Maps category to scope the search: a macro category (e.g. 'Restaurant', 'Construction & Home') or a single subcategory (e.g. 'Construction company'). Use the English name; case does not matter. An unknown name is refused with a 422 `UNKNOWN_CATEGORY` error.
    - `subcategory` string, nullable: Google Maps subcategory (e.g. 'Italian restaurant'), English name. Takes precedence over `category`. An unknown name is refused with a 422 `UNKNOWN_CATEGORY` error.
    - `cities` string[]: City names (e.g. ['Paris', 'Lyon']), in the signal's `country`. A city that is not in that country matches no business: it is accepted and listed in an `UNKNOWN_CITY` warning of the response.
    - `postalCodes` string[]: Postal codes in the selected country (e.g. ['75001', '69001']).
    - `minRating` number, nullable: Minimum Google rating.
    - `maxRating` number, nullable: Maximum Google rating.
    - `minReviews` integer, nullable: Minimum Google review count.
    - `maxReviews` integer, nullable: Maximum Google review count.
    - `hasEmail` boolean, nullable: Only return businesses that have a public email address.
    - `hasWebsite` boolean, nullable: Only return businesses that have a website.
    - `hasPhone` boolean, nullable: Only return businesses that have a phone number.
    - `hasSiret` boolean, nullable: Only return businesses that have a French SIRET identifier.
    - `hasFacebook` boolean, nullable: Only return businesses with a Facebook page.
    - `hasInstagram` boolean, nullable: Only return businesses with an Instagram account.
    - `hasLinkedin` boolean, nullable: Only return businesses with a LinkedIn page.
    - `hasYoutube` boolean, nullable: Only return businesses with a YouTube channel.
    - `hasTwitter` boolean, nullable: Only return businesses with an X (Twitter) account.
    - `hasTiktok` boolean, nullable: Only return businesses with a TikTok account.
    - `hasClaimed` boolean, nullable: Filter on whether the owner has claimed their Google Business listing. Set it to `false` for the more useful half: an unclaimed listing means nobody is managing that presence yet, which is a different conversation from selling to a business that already runs its own page. Claimed status is not known for every country we cover — where it is missing, this filter matches nothing, so leave it unset rather than narrowing a search that would otherwise return results.
    - `apeCodes` string[]: French APE/NAF codes to filter on.
    - `keepOnlyConfirmedNew` boolean: Premium filter. When true, every candidate is cross-checked against the SIRENE registry; businesses registered more than 6 months ago are dropped as false positives. Auto-entrepreneurs and businesses not present in SIRENE are kept. Switches per-lead pricing from 1 to 5 credits.
    - `confirmedNewThresholdDays` integer, nullable: Custom SIRENE freshness cutoff (in days) for keep_only_confirmed_new. Defaults to 180 when null. Lower it (e.g. 90) for verticals like food trucks where genuinely new openings dominate; raise it (e.g. 365) for longer-tail trades where SIRENE registration lags real opening. Only consulted when keep_only_confirmed_new is true.
    - `keepOnlyNewlyListed` boolean: Only deliver businesses that appeared on Google Maps recently, rather than every business matching your filters. Available in every country. Leave it off to receive all matching businesses we find; turn it on to receive only the newly listed ones.
    - `newlyListedThresholdDays` integer, nullable: How recently a business must have appeared on Google Maps to count as newly listed. Defaults to 180 when null. Only consulted when keepOnlyNewlyListed is true.
  - `dailyLeadLimit` integer: Max leads per day
  - `status` string, nullable: Initial status (default: draft).

## Response `201`

Signal created

- object: A single signal: create, GET /signals/{id} and PATCH. It carries two fields the list does not. A customer reads their stored `config` back and assumes it is the one in force; on `job_changes` three fields were not (`maxJobAgeDays` 90 stored / 180 applied, `publishedDate` stored and never read, `allowUnknownCompanySize` true and applied false), with nothing anywhere saying so.
  - `id` string, required
  - `name` string, required
  - `status` string, required
  - `signalTypeId` string, required
  - `webhookUrl` string, nullable
  - `config` object, nullable: Signal-specific configuration
  - `dailyLeadLimit` integer, required
  - `leadsToday` integer, required
  - `leadsTotal` integer, required
  - `duplicateHandling` string, required: Duplicate lead handling: 'refuse' (never accept), 'allow' (always accept), or 'Xd' (accept after X days)
  - `createdAt` string, required
  - `updatedAt` string, required
  - `lastLeadAt` string, nullable
  - `applied` object: Settings this signal runs with where they differ from `config`, keyed like `config`. `{}` when every stored setting is applied as stored. A `null` value means the stored field is not used by this signal. Every entry is explained by a warning naming the same field.
  - `warnings` SignalConfigWarning[]: What you should know about this configuration: values applied differently from what is stored, and filter values that are probably not what you meant. Computed from the stored configuration, so they appear on GET as well as on create and update - a signal created before these fields existed reports them too. Never an error: the signal is created or updated regardless.
    - `code` string, required: Stable identifier. `VALUE_BELOW_MINIMUM` (a value under the documented minimum is applied as the minimum), `VALUE_ADJUSTED` (a value the search cannot use as written is applied as the nearest one it can, e.g. a `publishedDate` of 14d searches 15 days), `FIELD_NOT_USED` (a stored field this signal ignores), `UNKNOWN_SIZE_NOT_ACCEPTED`, `CATEGORY_WITH_OWN_INDUSTRIES` (a category listed together with industries it already covers), `UNKNOWN_INDUSTRY` (a value matching no published industry), `UNKNOWN_FIELD` (a config key this signal does not have), `UNKNOWN_TOOL` / `UNKNOWN_CATEGORY` (a tracked tool or category name that is not in the catalog of GET /api/v1/signals/technographic-tools; on a GMB Discovery signal, a `category` that is not a Google Maps category, so the signal matches no business), `UNKNOWN_CITY` (GMB Discovery: cities in `cities` that are not in the signal's `country` — `values` lists them; when none is, the signal matches no business until `cities` or `country` is changed; never an error, nothing stored is changed), `NO_FOLLOWER_SOURCE` (a Company Followers signal with no follower extraction set up yet, so it produces nothing), `DAILY_LIMIT_PENDING` (a job signal's `dailyLeadLimit` changed less than a day after its search was last rebuilt: the search keeps its previous volume until the time in `values`, while the leads delivered per day already follow the new limit), `WATCHED_LIST_EMPTY` (a watchlist reading its list from your CRM holds no company or person — the CRM is not connected, the list could not be read, or it is empty — so it produces nothing until the list fills; cleared on its own once it does), `QUOTE_REQUIRED` (an `active` Company Followers signal in `icp` mode whose follower extraction has not been priced and accepted, so it produces nothing yet: `values[0]` says why — `not_requested`, `awaiting_acceptance` (then `values[1]` is the quoted price in credits), `expired`, `declined`, `failed`, or, after an automatic quote, `auto_quote_refused` / `auto_accept_refused` (then `values[1]` is the error code); the message names the call to make — see POST /api/v1/signals/{signal_id}/extraction/quote; cleared once a quote is accepted), `WATCHED_LIST_IMPORTING` (a company watchlist whose HubSpot list is still being read: companies are added as they arrive; `values[0]` is the number of members read so far, `values[1]` the list id; cleared once the whole list is read), `MATCHING_CONFIRMATION_REQUIRED` (a New Decision Maker or Account watchlist whose companies must be matched to their LinkedIn company page before they can be watched, at a cost above the amount we match without asking: nothing is matched or charged until you confirm with POST /api/v1/watchlists/{signalId}/companies/confirm-matching; `values[0]` is the maximum cost, `values[1]` the number of companies to match; cleared once confirmed), `DESTINATION_UNAVAILABLE` (the outreach campaign this signal sends leads to cannot receive them — not found, archived, ended or without any step — so leads are not reaching it: `values[0]` is `campaign_not_found`, `campaign_archived`, `campaign_ended` or `campaign_no_steps`; cleared once a lead reaches the campaign or the signal's destination is changed), `DESTINATION_NEEDS_CONTACT` (the signal delivers companies with no contact and a destination in `destinations` only accepts people, so none of its leads reach that destination), `DESTINATION_SKIPS_COMPANY_ONLY` (some leads are companies with no contact, e.g. no one matching your personas was found, and a destination only accepts people), `DESTINATION_NEEDS_EMAIL` (a destination only accepts leads with an email and `getEmails` is off) — for these three, `values[0]` is the destination key in `destinations`, the next values are the fixes: `add_persona` (turn on `enablePersonaTargeting` with `targetPersonas`), `require_contact` (turn on `requireContact`), `add_company_destination` (add a destination that accepts companies: `hubspot`, `pipedrive`, `googleSheets` or `customWebhook`), `enable_email_finding` (turn on `getEmails`); severity `warning` when those leads reach no destination at all, `info` when another destination still receives them; never blocks the save; `COMPANY_PAGE_UNAVAILABLE` (a Company Followers signal whose extraction stopped because a page in `linkedinCompanyUrls` does not exist on LinkedIn: nothing was extracted or charged; `values` lists the pages; replace them with PATCH /api/v1/signals/{signal_id}, then start the extraction again; cleared once the pages are replaced or a new extraction runs), `COMPANY_PAGE_UNVERIFIED` (create / PATCH responses only: a page in `linkedinCompanyUrls` could not be verified at that moment and was accepted; `values` lists them — if one does not exist, its extraction stops without using credits and the signal carries `COMPANY_PAGE_UNAVAILABLE`).
    - `severity` string, required: `info`: nothing to do. `warning`: probably not what you meant.
    - `field` string, required: The `config` key concerned, camelCase (or `dailyLeadLimit`).
    - `message` string, required
    - `values` string[]: The offending values, when there are some.

## Other responses

- `400`: Invalid request: missing required config, or an unrecognized location filter
- `401`: Missing or invalid API key
- `402`: Account has no credits
- `422`: Validation Error
- `429`: Rate limit exceeded. Retry after the number of seconds in the `Retry-After` response header.

---

[API](https://skmtc.dev/rodz/apis/rodz-api.md) · [All operations](https://skmtc.dev/rodz/apis/rodz-api/llms.txt) · [OpenAPI document](https://skmtc.dev/rodz/apis/rodz-api/revisions/3003505ff896?raw)
