# IBLead > B2B database of businesses listed on Google Maps (phone, email, website, rating, category; SIRET for France), searchable by country, city and category; exportable as CSV, via REST API or via an MCP server. ## Facts - Database size: 45M+ businesses - Countries covered: 45 - Fields per business: name, category and subcategory, address, city, postal code, country, GPS coordinates, phone (Google listing and website), emails found on the business website, website URL, Google rating and review count, Google Maps URL, social profiles (Facebook, Instagram, LinkedIn, X, YouTube, TikTok) when available - France only: SIRET/SIREN, APE/NAF code and legal data matched from the national company register (SIRENE), when a match is found - Filters: country, category (names accepted in 43 languages), subcategory, city, postal code, map area (polygon), rating, review count, has email / phone / website / social profile, phone type (mobile or landline) - Data source: public Google Maps listings, enriched with contact details found on each business's own website ## Pricing - 1 credit = 1 exported record. Searching, counting and previewing cost 0 credits. API and MCP access requires a credit balance above 0 or an active plan. - Surcharges: +1 credit per record when filtering on mobile phones (phone_type=mobile); +1 credit per exported or unlocked record that has SIRET data (France only), applied automatically, no flag needed. A French record with SIRET data therefore costs 2 credits. - Prices in EUR, excluding VAT: - Starter: €44/month, 10,000 credits (€4.40 per 1,000 records); billed yearly: €396/year - Pro: €89/month, 20,000 credits (€4.45 per 1,000 records); billed yearly: €801/year - Business: €179/month, 40,000 credits (€4.47 per 1,000 records); billed yearly: €1,611/year - Enterprise: €449/month, 100,000 credits (€4.49 per 1,000 records); billed yearly: €4,041/year - Annual billing: 25% off the monthly price. - Subscription credits expire after 90 days; credit packs never expire. - Free account: 200 credits at signup, no card required. - Pricing page: https://iblead.com/en/pricing ## Access - Dashboard (search, filters, CSV export): https://app.iblead.com - REST API: base URL https://app.iblead.com/api/v1/, authentication header `Authorization: Bearer sk_live_...` (create a key in the dashboard under API keys), documented rate limit 120 requests per minute per API key. Full reference: https://iblead.com/llms-full.txt. OpenAPI 3.1 spec: https://iblead.com/openapi.json - MCP server: https://mcp.iblead.com/mcp (Streamable HTTP, OAuth 2.1 sign-in with an IBLead account, no API key to paste). Works in Claude (web, desktop, Claude Code), ChatGPT (Business, Enterprise and Edu workspaces with developer mode), OpenAI Codex, Cursor and VS Code. Setup: https://iblead.com/en/automation - Python CLI: `pip install iblead-cli` (https://pypi.org/project/iblead-cli/) Count matching businesses (0 credits): ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/businesses?country=FR&category=restaurant&has_email=true&limit=1" # → {"data": [...], "total": , "limit": 1, "offset": 0} ``` Export 200 of them as CSV (1 credit per record; French records with SIRET data cost 2): ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"country":"FR","category":"restaurant","has_email":true,"limit":200,"include_reviews":false}' \ "https://app.iblead.com/api/v1/export" -o export.csv ``` ## Links - [Home](https://iblead.com/en) - [Pricing](https://iblead.com/en/pricing) - [API and MCP server](https://iblead.com/en/automation) - [FAQ](https://iblead.com/en/faq) - [Comparisons with other tools](https://iblead.com/en/compare) - [Saudi Arabia business directory](https://iblead.com/en/blog/saudi-arabia-business-directory) - [UAE business directory](https://iblead.com/en/blog/uae-business-directory) - [Full API reference](https://iblead.com/llms-full.txt) - [OpenAPI 3.1 spec](https://iblead.com/openapi.json) --- # API v1 reference ## Base URL ``` https://app.iblead.com/api/v1/ ``` ## Authentication Include your API key in the Authorization header: ``` Authorization: Bearer sk_live_... ``` ## Rate Limit 120 requests per minute per API key. ## Multi-language support The `category` and `subcategory` parameters accept names in 43 languages (all Google Maps languages). Resolution is automatic server-side. Examples: "Restauration" (fr) → Restaurant, "Gastronomie" (de) → Restaurant, "Comptable" (fr) → Accountant. ## Typical workflow: Count, then Export ```bash # Step 1: Count matching businesses (0 credits) curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/businesses?country=FR&category=restaurant&has_email=true&limit=1" # → { "total": 4000, ... } # Step 2: Export only 200 of them. Always set limit: without it the export # takes every matching record your credit balance allows. curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"country":"FR","category":"restaurant","has_email":true,"limit":200}' \ "https://app.iblead.com/api/v1/export" -o export.csv ``` --- ## Common Filter Parameters These filters can be used on most endpoints. List filters (`cities`, `postal_codes`, `ape_codes`) are comma-separated strings in GET query parameters and JSON arrays of strings in POST bodies (e.g. `"cities": ["Nice", "Antibes"]`); a plain string in a POST body is rejected with 422. | Parameter | Type | Description | |-----------|------|-------------| | category | string | Macro category (e.g. "Restaurant") | | subcategory | string | Specific sub-category | | country | string | Country code (e.g. "FR") | | cities | string (GET) / string[] (POST) | City names | | postal_codes | string (GET) / string[] (POST) | Postal codes (GET: "93100,93200"; POST: ["93100","93200"]) | | min_rating | float | Minimum rating (0-5) | | max_rating | float | Maximum rating (0-5) | | min_reviews | int | Minimum review count | | max_reviews | int | Maximum review count | | has_website | bool | Filter by website presence | | has_phone | bool | Filter by phone presence | | has_email | bool | Filter by email presence | | has_facebook | bool | Filter by Facebook presence | | has_instagram | bool | Filter by Instagram presence | | has_linkedin | bool | Filter by LinkedIn presence | | has_siret | bool | Filter by SIRET presence | | has_youtube | bool | Filter by YouTube presence | | has_twitter | bool | Filter by Twitter/X presence | | has_tiktok | bool | Filter by TikTok presence | | has_claimed | bool | Filter by Google Business claimed status | | phone_type | string | "mobile" or "landline" | | ape_codes | string (GET) / string[] (POST) | APE/NAF codes (France only) | --- ## Endpoints ### GET /businesses List businesses with optional filters. Returns paginated results. **Additional parameters:** | Parameter | Type | Description | |-----------|------|-------------| | include_reviews | bool | Include Google reviews in response (default true) | | limit | int | Results per page (1-200, default 50) | | offset | int | Row offset (SQL OFFSET, NOT a page number). For page N with limit L, use offset=(N-1)*L. Default 0. | | only_unlocked | bool | Return only businesses already unlocked by this user (0 credits, fresh data). Returns 400 for service keys and legacy country-access plans. Default false. | No page limit — increment offset by limit to paginate: page 1 → offset=0, page 2 → offset=limit, page 3 → offset=2*limit, etc. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/businesses?country=FR&category=restaurant&limit=10" ``` **Response:** ```json { "data": [ { "id": 12345, "name": "Le Petit Bistro", "category": "restaurant", "rating": 4.5, "review_count": 128, "phone": "+33 1 23 45 67 89", "address": "12 Rue de Rivoli, 75001 Paris", "website_url": "https://lepetitbistro.fr", "url": "https://maps.google.com/?cid=...", "latitude": 48.8566, "longitude": 2.3522, "is_unlocked": true } ], "total": 45678, "limit": 10, "offset": 0 } ``` --- ### GET /businesses/{id} Get a single business by its ID. Returns full details if unlocked. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | id | int | yes | Business ID (path parameter) | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/businesses/12345" ``` **Response:** ```json { "id": 12345, "place_id": "ChIJ...", "name": "Le Petit Bistro", "category": "restaurant", "rating": 4.5, "review_count": 128, "phone": "+33 1 23 45 67 89", "address": "12 Rue de Rivoli, 75001 Paris", "website_url": "https://lepetitbistro.fr", "url": "https://maps.google.com/?cid=...", "latitude": 48.8566, "longitude": 2.3522, "is_unlocked": true, "emails": ["contact@lepetitbistro.fr"], "phones": ["+33 1 23 45 67 89"], "google_phone": "+33 1 23 45 67 89", "website_emails": ["contact@lepetitbistro.fr"], "website_phones": ["+33 1 23 45 67 89"], "socials": { "facebook": "https://facebook.com/lepetitbistro", "instagram": "https://instagram.com/lepetitbistro" }, "confidence_score": 85 } ``` --- ### POST /unlock Unlock businesses to access full details. Debits 1 credit per new record (+1 per record with SIRET data, France only). **Mode 1:** provide `business_ids` to unlock specific businesses. **Mode 2:** provide `count` + filters to unlock matching businesses. | Parameter | Type | Description | |-----------|------|-------------| | business_ids | int[] | Array of business IDs (max 10,000) | | count | int | Number to unlock (1-10,000) | | + all common filters | | See Common Filter Parameters above | **Example:** ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"business_ids": [123, 456, 789]}' \ "https://app.iblead.com/api/v1/unlock" ``` **Response:** ```json { "unlocked": [123, 456], "already_unlocked": [789], "cost": 2, "new_balance": 98 } ``` --- ### POST /export Export businesses as CSV or JSON. Subject to daily export limits. > **Past exports:** Previously generated CSV files can be re-downloaded from the dashboard Export History page at `/dashboard/exports`. Your unlocked records remain accessible for 90 days with no additional credits required — use the `only_unlocked=true` filter on `GET /businesses` or the dashboard "Only my unlocked records" checkbox. | Parameter | Type | Description | |-----------|------|-------------| | + all common filters | | See Common Filter Parameters above | | format | string | "csv" or "json" (default "csv") | | limit | int | Max records. Set it explicitly: if omitted, the export takes all matching records up to your daily limit and credit balance. | | resume | bool | Continue from last cursor (default true) | | redownload | bool | Re-export previous data (default false) | | include_reviews | bool | Include Google reviews (default true). Reviews can make the CSV 10-20x larger — set to false for smaller files. | | async | bool | Async mode: returns 202 + job_id instead of streaming (default false) | | webhook_url | string | HTTPS URL — triggers async mode and POSTs the job result when the file is ready (see Export webhook verification below) | | only_unlocked | bool | Re-export records you already unlocked (not expired) at 0 credits. Returns 400 for service keys and legacy country-access plans. Default false. | **Sync example:** ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"country": "FR", "category": "restaurant", "limit": 200, "format": "csv"}' \ "https://app.iblead.com/api/v1/export" -o export.csv ``` **Sync response headers:** - `X-Export-Count`: Number of records exported - `X-Daily-Remaining`: Daily limit remaining - `X-Daily-Limit`: Total daily limit **Async example:** ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"country": "FR", "category": "restaurant", "limit": 5000, "async": true}' \ "https://app.iblead.com/api/v1/export" ``` **Async response (202 Accepted):** ```json { "job_id": "abc123", "status": "pending", "poll_url": "/api/v1/export/jobs/abc123" } ``` --- ### GET /export/jobs/{job_id} Poll the status of an async export job. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | job_id | string | yes | Job ID from async export (path parameter) | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/export/jobs/abc123" ``` **Response:** ```json { "status": "completed", "download_url": "https://...presigned-download-url...", "format": "csv", "count": 5000, "credits_used": 5000, "total_matching": 12840, "requested": 5000, "partial": false, "completed_at": "2026-10-19T12:00:00+00:00" } ``` > **Statuses:** `pending` (queued), `processing`, `completed`, `failed` (with an `error` message). The download URL is a signed link valid for 24 hours; jobs expire after 24 hours. When `partial` is true, `partial_reason` says why (`insufficient_credits`, `user_limit_reached` or `daily_limit_reached`). --- ### Export webhook verification When an export request includes `webhook_url`, IBLead POSTs the completed job result (the same JSON as `GET /export/jobs/{job_id}`, plus `job_id`) to that HTTPS URL when the file is ready. Delivery is tried up to 3 times with a 10-second timeout. Failed jobs are not sent: poll the job to see them. Each delivery carries two headers: - `X-IBLead-Timestamp`: Unix time in seconds - `X-IBLead-Signature`: `sha256=`, the HMAC-SHA256 of `.` keyed with the API key (`sk_live_...`) that created the export Verify the signature on the raw request body before parsing it, and reject old timestamps: ```python import hashlib, hmac, time def verify_iblead_webhook(raw_body: bytes, timestamp: str, signature: str, api_key: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False expected = hmac.new(api_key.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest("sha256=" + expected, signature) ``` > **Watch webhooks** use a different scheme: header `X-Webhook-Signature: sha256=`, the HMAC-SHA256 of the raw body keyed with the watch's `webhook_secret` (returned once, when the watch is created). There is no timestamp header. --- ### GET /export/cursor Get export cursor state for a country. Use to track export progress. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | country | string | yes | Country code (e.g. "FR") | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/export/cursor?country=FR" ``` **Response:** ```json { "last_exported_id": 98765, "total_exported": 5000, "country_total": 125430 } ``` --- ### GET /account Get your account info: credits, plan, daily export usage. > **Credit expiration:** Subscription credits expire 90 days after grant. Credit packs and admin top-ups never expire. Expired credits cannot be used and do not carry over. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/account" ``` **Response:** ```json { "user_id": 42, "email": "user@example.com", "tier": "premium", "credits": 1500, "lifetime_countries": [], "daily_export_used": 3500, "daily_export_limit": 10000, "daily_export_remaining": 6500 } ``` > **Note:** For credit-based accounts the effective export limit is your credit balance. `daily_export_limit` is enforced only for legacy country-access plans or when a per-account limit has been set. --- ### GET /categories List macro categories with business counts. Accepts all common filters. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/categories?country=FR" ``` **Response:** ```json { "data": [ { "category": "Restaurant", "count": 45678 }, { "category": "Shopping & Retail", "count": 34521 }, { "category": "Health & Medical", "count": 28934 } ] } ``` --- ### GET /subcategories List subcategories with search and pagination. | Parameter | Type | Description | |-----------|------|-------------| | category | string | Filter by macro category | | search | string | Search term | | limit | int | Results per page (1-100, default 30) | | offset | int | Row offset (SQL OFFSET, NOT a page number). For page N with limit L, use offset=(N-1)*L. Default 0. | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/subcategories?category=Restaurant&search=italian" ``` **Response:** ```json { "data": [ { "subcategory": "Italian restaurant", "count": 5432 }, { "subcategory": "Italian grocery store", "count": 312 } ] } ``` --- ### GET /category-tree Return the full category hierarchy (29 macro categories → 4037 subcategories). **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/category-tree" ``` **Response:** ```json { "data": { "Restaurant": ["Italian restaurant", "Chinese restaurant", "Fast food", "..."], "Shopping & Retail": ["Clothing store", "Supermarket", "..."], "Health & Medical": ["Dentist", "Doctor", "Pharmacy", "..."] } } ``` --- ### GET /countries List available countries with business counts. Accepts all common filters. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/countries" ``` **Response:** ```json { "data": [ { "country_code": "FR", "count": 1234567 }, { "country_code": "DE", "count": 987654 }, { "country_code": "ES", "count": 654321 } ] } ``` --- ### GET /cities List cities for a country with search and pagination. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | country | string | yes | Country code (e.g. "FR") | | search | string | no | Search term | | limit | int | no | Results per page (1-100, default 30) | | offset | int | no | Row offset (SQL OFFSET, NOT a page number). For page N with limit L, use offset=(N-1)*L. Default 0. | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/cities?country=FR&search=Paris" ``` **Response:** ```json { "data": [ { "city": "Paris", "count": 45678 }, { "city": "Paris 1er Arrondissement", "count": 1234 } ] } ``` --- ### GET /postal-codes List postal codes for a country with prefix search and pagination. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | country | string | yes | Country code (e.g. "FR") | | search | string | no | Postal code prefix (e.g. "93" for all 93xxx) | | limit | int | no | Results per page (1-100, default 30) | | offset | int | no | Row offset (SQL OFFSET, NOT a page number). For page N with limit L, use offset=(N-1)*L. Default 0. | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/postal-codes?country=FR&search=93" ``` **Response:** ```json { "data": [ { "postal_code": "93100", "count": 5234 }, { "postal_code": "93200", "count": 3456 }, { "postal_code": "93300", "count": 2890 } ] } ``` --- ## Watches (Saved Searches) Save search filters and get notified when new businesses match. Optionally receive webhook notifications. ### POST /watches Create a watch (saved search). Max 10 watches per account. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | name | string | no | Display name for this watch | | category | string | yes* | Macro category (*at least category or country required) | | country | string | yes* | Country code (*at least category or country required) | | + common filters | | | See Common Filter Parameters above, except `has_claimed` (not accepted here; unknown fields return 422) | | bbox_wkt | string | no | WKT POLYGON for zone watch | | webhook_url | string | no | HTTPS URL to receive notifications | | auto_notify | bool | no | Enable webhook notifications (default false) | | since_now | bool | no | Only track future businesses (default true) | **Example:** ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"name":"Restaurants Nice","category":"Restaurant","country":"FR","cities":["Nice"],"since_now":true}' \ "https://app.iblead.com/api/v1/watches" ``` **Response:** ```json { "id": 42, "name": "Restaurants Nice", "filters": {"category": "Restaurant", "country": "FR", "cities": ["Nice"]}, "webhook_url": null, "webhook_secret": "whsec_abc123...", "auto_notify": false, "cursor_id": 12345678, "new_count": 0, "is_active": true, "created_at": "2026-02-24T12:00:00Z" } ``` > **Note:** `webhook_secret` is only returned on creation. Use it to verify webhook signatures (`X-Webhook-Signature` header). --- ### GET /watches List your active watches with the count of new businesses per watch. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/watches" ``` **Response:** ```json { "data": [ { "id": 42, "name": "Restaurants Nice", "filters": {"category": "Restaurant", "country": "FR"}, "new_count": 15, "is_active": true, "created_at": "2026-02-24T12:00:00Z" } ] } ``` --- ### GET /watches/{id} Get details of a single watch including the count of new businesses. **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/watches/42" ``` --- ### GET /watches/{id}/new Poll new businesses that match the watch filters since last check. Advances the cursor (unless `peek=true`). | Parameter | Type | Description | |-----------|------|-------------| | limit | int | Max results (1-1000, default 200) | | peek | bool | If true, don't advance the cursor (default false) | **Example:** ```bash curl -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/watches/42/new?limit=100" ``` **Response:** ```json { "data": [ {"id": 12345679, "name": "Le Petit Bistro", "category": "Restaurant"}, {"id": 12345680, "name": "Chez Marcel", "category": "Restaurant"} ], "count": 2, "cursor_id": 12345680, "has_more": false } ``` > **Note:** Results are blurred (id, name, category only) for credit-based accounts. Legacy country-access accounts receive full details. Use `/unlock` to access full data. --- ### DELETE /watches/{id} Deactivate a watch (soft delete). **Example:** ```bash curl -X DELETE -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/watches/42" ``` **Response:** ```json {"ok": true} ``` --- ### POST /watches/{id}/unlock Unlock new businesses from a watch and download as CSV in a single call. Debits credits and advances the cursor. | Parameter | Type | Description | |-----------|------|-------------| | id | int | Watch ID (path parameter) | | mode | string | "new" (since cursor, advances it) or "all" (all matching, no cursor advance) | | include_reviews | bool | Include Google reviews (default true) | | include_siret | bool | Include SIRET data, +1 credit/record (default false) | **Example:** ```bash curl -X POST -H "Authorization: Bearer sk_live_..." \ "https://app.iblead.com/api/v1/watches/42/unlock?mode=new&include_reviews=false" -o new_businesses.csv ``` **Response headers:** - `X-Records-Count`: Number of records in the CSV - `X-Credits-Used`: Credits debited - `X-Credits-Balance`: Remaining credit balance > **Credit accounts:** records are unlocked in batches of 500 and the stream stops if credits run out. Legacy country-access accounts stream directly with no credit debit.