GMB Club

Developers

Documentation API

The GMB Club API lets you publish and schedule posts on your social networks via Zapier, Make, or your own scripts. It also lets you retrieve generated articles.

HTTPSJSON APIRESTfulRate Limited

Key concepts

The v2 API uses GMB Club's hierarchical Sphere / Bubble architecture.

ConceptDescriptionExample
SphereBrand / Company / Franchisor"Auto Sud"
BubbleLocation / Point of sale"Auto Sud Marseille"
ResourcesAccounts connected to the bubbleFacebook, Instagram, GMB
💡

Important

An API key covers either a single location (bubble key) or all the brand's locations (sphere key). A bubble key sees only its own location's data; a sphere key sees data for all of them. Call GET /api/v2/me first: the « bulle » field is filled in for a bubble key and is null for a sphere key.

Authentication

The API uses API keys for authentication. A key can have two scopes: it covers either a single location (bubble key) or all the brand's locations (sphere key). A bubble key sees only its own location's data; a sphere key sees data for all locations — the ideal option for a multi-location website: a single key, a single integration. Call GET /api/v2/me first to find out which scope you have: the « bulle » field is filled in for a bubble key and is null for a sphere key.

⚠️

Key scope: bubble or sphere

A single scope rule, valid across the whole API:

  1. A key covers one location (bubble key) or the entire brand (sphere key). It defines what is allowed, never what is targeted.
  2. Any route that acts on a target accepts a target parameter: bulle_id for a location, fiche_id for a Google listing. Always in the same place: URL parameter for reads, body field for writes.
  3. Without the parameter: a bubble key targets its location; a sphere key gets a 400 that names the missing parameter. Never an arbitrary choice.
  4. List routes without a parameter return the whole brand, and each item carries its bulle_id.
  5. A target outside the key's scope returns 403; a nonexistent target, 404.

Possible responses (backend messages, in French):

CodeResponse (detail)
403Cet établissement n'appartient pas à votre périmètre.
403Cette fiche GMB n'appartient pas à votre périmètre.
404Établissement introuvable : <identifiant>
404Aucune fiche GMB associée à cette clé API.
400Cette clé API couvre plusieurs établissements. Précisez `bulle_id` (liste : GET /api/v2/bulles).
400Cette clé API couvre plusieurs fiches Google. Précisez `fiche_id` (liste : GET /api/v2/me).

Get an API key

  1. Sign in to GMB Club
  2. Select your bubble in the header
  3. Settings → API keys → New key
  4. Choose the scope: "This location" or "Whole brand"
  5. Configure permissions and copy the key

A "Whole brand" key covers every location, including those created later: it's the right choice for a single website that serves several locations — one secret to manage, and nothing to do when a new location opens.

⚠️

Important

The full key is only shown once during creation. Store it securely.

Key format

text
gmb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Usage

cURL
curl -H "X-API-Key: gmb_live_xxxx..." \
     -H "Content-Type: application/json" \
  https://app.gmb-club.com/api/v2/me
🚫

JSON only

The API accepts JSON exclusively. The Content-Type: application/json header is required for all POST/PUT requests.

Base URL

text
https://app.gmb-club.com/api/v2

Endpoints

GET/api/v2/health

Checks that the API is operational. No authentication required.

JSON
{
  "status": "ok",
  "api_version": "2.0",
  "timestamp": "2026-07-27T18:00:00.000000Z"
}

Key identity

GET/api/v2/meAuthread

Returns info about the API key, bubble, sphere and connected resources. Call this endpoint first.

JSON
{
  "api_version": "2.0",
  "bulle": { "id": "abc-123", "name": "Auto Sud Marseille", "city": "Marseille" },
  "sphere": { "id": "xyz-789", "name": "Auto Sud", "slug": "auto-sud" },
  "permissions": ["read", "schedule", "publish"],
  "rate_limits": { "per_minute": 60, "per_day": 1000 },
  "connected_platforms": ["facebook", "instagram", "gmb", "linkedin"],
  "gmb_fiches": [{
    "id": "accounts/115473832376124235338/locations/12517715818229047311",
    "name": "Auto Sud - Marseille Centre",
    "address": "12 rue de la Paix, 13001, Marseille",
    "service_area": false
  }]
}
💡

Sphere key

With a sphere key, the bulle field is null and the gmb_fiches array lists all the brand's listings.

Social connections

GET/api/v2/connectionsAuthread

Returns social network connection details for this bubble.

JSON
[
  { "platform": "facebook", "connected": true, "name": "Auto Sud Marseille", "bulle_id": "abc-123", "expires_at": "2025-12-15T10:00:00", "needs_reconnect": false },
  { "platform": "instagram", "connected": true, "name": "autosud_marseille", "bulle_id": "abc-456", "expires_at": "2025-12-15T10:00:00", "needs_reconnect": false }
]
ParameterTypeDefault
bulle_idstringRestrict to one location. Default: the key's location, or every location of the brand.

With a sphere key, bulle_id indicates which location each Facebook page or Instagram account belongs to.

Platform limits

GET/api/v2/platforms/limitsAuthread

Returns character and media limits for each platform.

JSON
{
  "character_limits": { "<plateforme>": "<int>" },
  "image_limits": {
    "<plateforme>": { "max_size_mb": "<int>", "carousel_min": "<int?>", "carousel_max": "<int?>" }
  },
  "video_limits": {
    "<plateforme>": { "max_size_mb": "<int>", "max_duration_sec": "<int?>" }
  }
}
⚠️

Shape, not frozen values

character_limits contains one entry per platform (9 in total); image_limits and video_limits cover instagram, facebook, linkedin, pinterest, gmb. The values are derived from the code and change over time: call the endpoint for up-to-date figures, don't hard-code them into your integration.

Create a post

POST/api/v2/postsAuthschedule

Creates a new scheduled post or publishes immediately.

🚫

Common errors

Content-Type must be application/json. Platforms in lowercase. image_urls = real JSON array.
⚠️

Sphere key: bulle_id required

With a sphere key, bulle_id is now required on POST /posts. Previously the call succeeded by picking a default location, which could publish to the wrong Google listing. Existing integrations that use a sphere key must add this field.

Body (JSON)

JSON
{
  "content": "Discover our new arrivals! 🚗 #automobile",
  "platforms": ["facebook", "instagram", "gmb"],
  "image_url": "https://example.com/images/promo.jpg",
  "scheduled_at": "2025-12-01T10:00:00+01:00",
  "gmb_fiche_id": "gmb-fiche-123-456"
}

Parameters

FieldTypeRequiredDescription
contentstringYesPost text (1-5000 chars)
platformsarrayYesArray: facebook, instagram, gmb, linkedin, pinterest, tiktok, snapchat, youtube. The platform must be connected on the location, otherwise HTTP 400 « Plateforme non connectée ».
image_urlstringNoSingle image URL
image_urlsarrayNoJSON URL array for carousel (2-10)
video_urlstringNoVideo URL
scheduled_atstringNoISO 8601 date/time (default: +1h)
gmb_fiche_idstringNoGMB listing ID (see /me)
publish_nowbooleanNoIf true, publish immediately
bulle_idstringSphere keyRequired with a sphere key: the targeted location. Inferred automatically with a bubble key.
💡

content or text

Provide at least content (or its alias text). Per-platform scheduling is available via scheduled_instagram, scheduled_facebook, scheduled_gmb, etc., and per-platform content via content_per_platform.

Response:

JSON
{
  "id": 42,
  "content": "Discover our new arrivals! 🚗 #automobile",
  "platforms": ["facebook", "instagram", "gmb"],
  "status": "scheduled",
  "image_url": "https://example.com/images/promo.jpg",
  "scheduled_gmb": "2025-12-01T10:00:00+01:00",
  "created_at": "2025-11-29T18:00:00Z",
  "updated_at": "2025-11-29T18:00:00Z",
  "warnings": []
}

List posts

GET/api/v2/postsAuthread

Returns bubble posts with pagination.

ParameterTypeDefaultDescription
statusstring-pending, published, error
limitinteger501-100
offsetinteger0Pagination offset
bulle_idstring-Restrict to one location. Default: the key's location, or every location of the brand.
⚠️

Bare array

This endpoint returns a JSON array directly, with no wrapper object — unlike GET /articles which returns an object { articles, total, ... }.
JSON
[
  {
    "id": 42,
    "bulle_id": "abc-123",
    "content": "Discover our new arrivals! 🚗",
    "platforms": ["facebook", "gmb"],
    "status": "published",
    "image_url": "https://example.com/images/promo.jpg",
    "image_urls": null,
    "video_url": null,
    "scheduled_instagram": null,
    "scheduled_facebook": "2025-12-01T10:00:00+01:00",
    "scheduled_gmb": "2025-12-01T10:00:00+01:00",
    "scheduled_linkedin": null,
    "scheduled_pinterest": null,
    "scheduled_tiktok": null,
    "scheduled_snapchat": null,
    "scheduled_youtube": null,
    "published_instagram": null,
    "published_facebook": "2025-12-01T10:00:05+01:00",
    "published_gmb": "2025-12-01T10:00:05+01:00",
    "published_linkedin": null,
    "published_pinterest": null,
    "published_tiktok": null,
    "published_snapchat": null,
    "published_youtube": null,
    "error_instagram": null,
    "error_facebook": null,
    "error_gmb": null,
    "error_linkedin": null,
    "error_pinterest": null,
    "error_tiktok": null,
    "error_snapchat": null,
    "error_youtube": null,
    "platform_refs": { "facebook": "...", "gmb": "..." },
    "created_at": "2025-11-29T18:00:00Z",
    "updated_at": "2025-12-01T10:00:05Z",
    "warnings": []
  }
]
💡

Full response, field by field

Three clarifications. There is a scheduled_ / published_ / error_ trio per platform: the scheduled date, the actual publication date, and the error message, if any. A platform not involved in a given post has its three fields set to null. platform_refs holds the references of the publications on each platform once published; warnings, the non-blocking warnings.
GET/api/v2/posts/{post_id}Authread

Retrieves a post by its identifier. Returns the same object as an element of GET /api/v2/posts.

PUT/api/v2/posts/{post_id}Authschedule

Updates a pending post (status: pending only).

💡

Partial update

Partial update of a pending post (content, scheduled_at). Only the fields you send are updated.

Body (JSON)

JSON
{
  "content": "Updated text!",
  "scheduled_at": "2025-12-02T14:00:00+01:00"
}
DEL/api/v2/posts/{post_id}Authdelete

Deletes a pending or errored post.

POST/api/v2/posts/{post_id}/publishAuthpublish

Forces immediate publication of a scheduled post.

Simplified social publishing

Three endpoints designed for Make, n8n and Zapier integrations: publish, check the quota, track the status. They all require a bubble key (see « Key scope »).

POST/api/v2/social/publishAuthpublish

Publishes or schedules a post on one or more platforms.

Body (JSON)

FieldTypeRequired
platformsarrayYes
textstringYes
image_urlstringNo
image_urlsarrayNo
video_urlstringNo
scheduled_atstringNo
bulle_idstringSphere key

platforms: facebook, instagram, linkedin, pinterest, tiktok, snapchat, youtube, gmb. text: 1 to 63206 characters. image_url for a single image, image_urls for a carousel. scheduled_at (ISO 8601) absent = immediate publication.

Response:

JSON
{
  "post_id": "post-abc-123",
  "status": "scheduled",
  "scheduled_at": "2026-08-01T09:00:00+02:00",
  "platforms": ["facebook", "instagram"],
  "quota": {}
}

status is publishing (immediate publication) or scheduled (scheduled).

GET/api/v2/social/quotaAuthread

The location's monthly publishing quota status. Call it before publishing to avoid a rejection.

JSON
{ "bulle_id": "abc-123" }

The response is supplemented with the location's monthly quota counters.

bulle_idRequired with a sphere key (the targeted location). Optional with a bubble key.

GET/api/v2/social/posts/{post_id}Authread

Detailed status of a post. The scheduled, published and errors objects carry one entry per platform, null if the platform is not concerned.

JSON
{
  "post_id": "post-abc-123",
  "status": "scheduled",
  "platforms": ["facebook", "instagram"],
  "content": "Nos nouveautés sont arrivées !",
  "scheduled": { "instagram": "2026-08-01T09:00:00+02:00", "facebook": "2026-08-01T09:00:00+02:00", "linkedin": null, "pinterest": null, "tiktok": null, "snapchat": null, "youtube": null, "gmb": null },
  "published": { "instagram": null, "facebook": null, "linkedin": null, "pinterest": null, "tiktok": null, "snapchat": null, "youtube": null, "gmb": null },
  "errors": { "instagram": null, "facebook": null, "linkedin": null, "pinterest": null, "tiktok": null, "snapchat": null, "youtube": null, "gmb": null }
}

List articles

GET/api/v2/articlesAuthread

Returns generated articles for this bubble. Ideal for custom site integration.

💡

Site without your website (major CMS supported)

Call this endpoint periodically to fetch articles and display on your site.
JSON
{
  "articles": [{
    "id": 12,
    "title": "5 tips for maintaining your car",
    "excerpt": "Expert tips to extend your vehicle's life.",
    "content_html": "<h2>Introduction</h2><p>Regular maintenance...</p>",
    "featured_image_url": "https://storage.example.com/images/article-12.jpg",
    "meta_title": "5 car maintenance tips | Auto Sud Marseille",
    "meta_description": "Our experts share their best car maintenance tips.",
    "target_keyword": "car maintenance Marseille",
    "status": "published",
    "published_at": "2026-03-10T14:00:00Z",
    "created_at": "2026-03-08T09:00:00Z",
    "updated_at": "2026-03-12T08:30:00Z",
    "slug": "5-conseils-pour-entretenir-sa-voiture",
    "bulle_id": "abc-123",
    "bulle_name": "Auto Sud Marseille"
  }],
  "total": 24, "limit": 10, "offset": 0
}
💡

Multi-location website

Each article indicates the location it was written for. With a sphere key, a single call returns the articles for all locations, and bulle_id indicates which page to publish each one on. bulle_id is null if the article is attached only to the brand, with no location. The full location mapping is obtained via GET /api/v2/bulles.

Parameters

ParameterTypeDefault
statusstringpublished · draft · all
updated_sincestringISO 8601 — articles modified since this date (filters on updated_at, sorted oldest to newest).
bulle_idstringRestrict to one location. Default: the key's location, or every location of the brand.
💡

Stay in sync

Detect a correction. since filters on the publication date: an article published a month ago and then corrected this morning won't show up. To catch corrections, use updated_since, which filters on updated_at. Results are then sorted oldest to newest, which lets you resume a sync exactly where it stopped by keeping the last value received.

Build your URLs from the slug, never from the title. The slug is locked at first publication and is never recomputed, even if the title is corrected afterward. Deriving the URL from the title would expose your pages to an address change after indexing.

Detect a removal. An unpublished article no longer appears under status=published. With status=all, it comes back to you with its actual status ("draft"), an explicit signal. A deleted article, on the other hand, disappears entirely: treat the list you receive as the source of truth — whatever is no longer in it is no longer online.

GET/api/v2/articles/{article_id}Authread

Retrieves full article content by ID.

JSON
{
  "id": 12,
  "title": "5 conseils pour entretenir sa voiture",
  "excerpt": "Nos conseils d'expert.",
  "content_html": "<h2>Introduction</h2><p>...</p>",
  "featured_image_url": "https://storage.example.com/images/article-12.jpg",
  "meta_title": "5 conseils entretien | Auto Sud",
  "meta_description": "Nos experts partagent leurs conseils.",
  "target_keyword": "entretien voiture marseille",
  "status": "published",
  "published_at": "2026-03-10T14:00:00Z",
  "created_at": "2026-03-08T09:00:00Z",
  "updated_at": "2026-03-12T08:30:00Z",
  "slug": "5-conseils-pour-entretenir-sa-voiture",
  "bulle_id": "abc-123",
  "bulle_name": "Auto Sud Marseille"
}

Confirm article rendering

POST/api/v2/articles/{article_id}/confirmAuthread

Pingback sent by your script after rendering a fetched article. It has two effects: 1. it lets GMB Club know which articles are actually live on your site, as opposed to merely exposed by the API; 2. by sending `rendered_url`, it feeds the internal linking of your articles — the more useful effect day to day. Optional but recommended.

💡

When to call it

After injecting the article's HTML into your page's DOM. The endpoint is idempotent: calling it again simply updates the confirmation timestamp, last ping wins. You can therefore call it again to check that an article is still rendered after several days.

Optional JSON body. The `rendered_url` field (string, max 2048 chars, must start with http:// or https://) is the public URL of the page where your script has just rendered the article. It is the only address of your site that GMB Club knows: it lets your future articles cite earlier ones with a link that points to the right place. Without it, no internal linking is possible on your site — we never guess a URL, and an invented link would be a dead link. Technically optional, but strongly recommended.

JavaScript
// Après avoir affiché l'article dans le DOM
fetch(`https://app.gmb-club.com/api/v2/articles/${article.id}/confirm`, {
  method: "POST",
  headers: {
    "X-API-Key": "gmb_live_xxxx...",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ rendered_url: location.href })
});
💡

CORS tip

The articles endpoints accept cross-origin requests from any domain (Access-Control-Allow-Origin: *). You can call the pingback directly from the <script> on your page, or server-side during a static build or SSR render, whichever fits your stack.
PHP
$ch = curl_init("https://app.gmb-club.com/api/v2/articles/{$id}/confirm");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "rendered_url" => "https://monsite.fr/blog/article-{$id}"
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-API-Key: gmb_live_xxxx...",
    "Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
Node.js
await fetch(`https://app.gmb-club.com/api/v2/articles/${id}/confirm`, {
  method: "POST",
  headers: {
    "X-API-Key": process.env.GMB_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ rendered_url: `https://monsite.fr/blog/article-${id}` }),
});
Python
import requests
requests.post(
    f"https://app.gmb-club.com/api/v2/articles/{id}/confirm",
    headers={"X-API-Key": "gmb_live_xxxx...", "Content-Type": "application/json"},
    json={"rendered_url": f"https://monsite.fr/blog/article-{id}"},
)
cURL
curl -X POST \
  -H "X-API-Key: gmb_live_xxxx..." \
  -H "Content-Type: application/json" \
  -d '{"rendered_url": "https://monsite.fr/blog/123"}' \
  https://app.gmb-club.com/api/v2/articles/42/confirm

200 response on successful confirmation:

JSON
{
  "article_id": 42,
  "confirmed_at": "2026-05-25T14:30:00Z",
  "confirmation_source": "pingback"
}

Internal linking of your articles

When you confirm the address of your published articles, GMB Club can link them together: a new article will naturally cite earlier ones, on a phrase from its text, with a link to the right page of your site. The scope is the location, not the brand: the articles of one bubble are only linked to each other, and a chain with several locations will never see an article from one city point to another city. Only articles that are published and whose address has been confirmed are eligible; an article without a confirmed address is simply ignored. The benefit builds over time: with two or three articles online there is almost nothing to link, the value comes with volume.

Cross-cutting conventions

Three mechanisms recur across most v2 endpoints. Understanding them once saves a lot of errors.

Risk-free preview: dry_run

Every costly operation (replying to a review, publishing, SEO scan, report, sending review requests, auditing or applying listing changes) accepts "dry_run": true in the body. The API then returns a preview (would_* fields, current quota) without executing anything or consuming quota. Recommended in your Make or n8n scenarios to validate a call before running it for real.
💡

Default listing: optional fiche_id

On listing-level endpoints (metrics, reviews, optimizer, acquisition, visibility), the fiche_id parameter is optional: if omitted, the API uses the listing attached to your API key.
💡

Long operations: 202 response

Long-running jobs (report generation, review requests, article generation) return a 202 code with an identifier. Fetch the result afterwards via the matching read endpoints (for example GET /v2/reports for a report, or GET /v2/articles for an article).

Workspace

Key's sphere

GET/api/v2/spheresAuthread

Returns the sphere (brand) attached to the API key: name, slug, default tone and language.

JSON
[
  {
    "id": "xyz-789",
    "name": "Auto Sud",
    "slug": "auto-sud",
    "default_language": "fr",
    "default_tone": "professionnel",
    "created_at": "2026-01-15T09:00:00Z"
  }
]

Sphere's locations

GET/api/v2/bullesAuthread

Lists the bubbles (locations) of the sphere, with their city and the number of attached Google listings.

JSON
[
  { "id": "abc-123", "name": "Auto Sud Marseille", "city": "Marseille", "tone": "professionnel", "language": "fr", "sphere_id": "xyz-789", "created_at": "2026-01-20T11:00:00Z" },
  { "id": "abc-456", "name": "Auto Sud Aix", "city": "Aix-en-Provence", "tone": "professionnel", "language": "fr", "sphere_id": "xyz-789", "created_at": "2026-01-20T11:05:00Z" }
]

Location details

GET/api/v2/bulles/{bulle_id}Authread

Returns a bubble's details: effective tone and language (inherited from the sphere if unset), number of Google listings, connected networks.

JSON
{
  "id": "abc-123",
  "name": "Auto Sud Marseille",
  "city": "Marseille",
  "tone": "professionnel",
  "language": "fr",
  "sphere_id": "xyz-789",
  "created_at": "2026-01-20T11:00:00Z",
  "effective_tone": "professionnel",
  "effective_language": "fr",
  "gmb_fiches_count": 2
}

Google reviews

List reviews

GET/api/v2/reviewsAuthread

Returns the listing's Google reviews, paginated and filterable (rating, reply status, keyword, dates), sortable by date or rating.

Parameters

ParameterTypeDefault
min_starsinteger1-5 · 1
max_starsinteger1-5 · 5
statusstringresponded · drafts · unresponded
keywordstring-
date_fromstringISO 8601
date_tostringISO 8601
sort_bystringdate_desc
fiche_idstringDefault listing: optional fiche_id
limitinteger1-100 · 25
pageinteger1
JSON
{
  "fiche": { "id": "gmb-fiche-123", "name": "Auto Sud - Marseille Centre", "address": "12 rue de la Paix, Marseille", "place_id": "ChIJ...", "average_rating": 4.6, "total_reviews": 128 },
  "reviews": [{
    "review_id": "rev-abc",
    "customer_name": "Julie A.",
    "rating": 5,
    "text": "Service impeccable, je recommande !",
    "review_timestamp": "2026-07-01T09:12:00Z",
    "response_text": null,
    "reply_date": null,
    "responded": false,
    "author_photo": "https://lh3.googleusercontent.com/..."
  }],
  "average_rating": 4.6,
  "total_reviews": 128,
  "filtered_count": 1,
  "status_counts": { "all": 128, "responded": 90, "drafts": 12, "unresponded": 26 },
  "page": 1,
  "total_pages": 3
}

Reviews summary

GET/api/v2/reviews/summaryAuthread

Returns the average rating, total number of reviews and their breakdown by status (responded, drafts, unresponded).

JSON
{
  "fiche": { "id": "gmb-fiche-123", "name": "Auto Sud - Marseille Centre" },
  "average_rating": 4.6,
  "total_reviews": 128,
  "status_counts": { "all": 128, "responded": 90, "drafts": 12, "unresponded": 26 }
}

Reply to a review

POST/api/v2/reviews/{review_id}/replyAuthpublish

Publishes a reply to a review directly on Google. Costly operation: supports dry_run.

Body (JSON)

JSON
{
  "text": "Merci beaucoup pour votre retour, à très bientôt !",
  "dry_run": false
}
⚠️

410 Gone

If the review has disappeared from Google in the meantime, the API returns 410.

Response:

JSON
{
  "posted": true,
  "review_id": "rev-abc",
  "rating": 5,
  "customer_name": "Julie A.",
  "fiche_name": "Auto Sud - Marseille Centre"
}

Review acquisition

Acquisition stats

GET/api/v2/acquisition/statsAuthread

Returns review-request statistics over 30 days: number sent, by channel (SMS, email) and by status.

JSON
{
  "fiche_id": "gmb-fiche-123",
  "total_requests": 84,
  "by_channel": { "sms": 50, "email": 34 },
  "by_status": {},
  "last_30_days": 84
}

Send review requests

POST/api/v2/acquisition/review-requestsAuthpublish

Sends review requests by SMS and/or email to a list of contacts. Subject to monthly quotas. 202 response with a batch_id; supports dry_run.

Body (JSON)

JSON
{
  "contacts": [
    { "customer_name": "Julie A.", "channel": "sms", "customer_phone": "+33612345678" },
    { "customer_name": "Marc D.", "channel": "email", "customer_email": "[email protected]", "custom_message": "Merci pour votre visite !" }
  ],
  "dry_run": false
}
💡

contacts[]

Each contact carries at least a name (customer_name) and a channel (channel): "sms" requires customer_phone, "email" requires customer_email. Optional field: custom_message. SMS and email quotas are monthly and per bubble.

Response:

JSON
{
  "queued": true,
  "queued_count": 2,
  "batch_id": "batch-abc-123",
  "contacts": [
    { "customer_name": "Julie A.", "channel": "sms", "contact": "+33612345678" },
    { "customer_name": "Marc D.", "channel": "email", "contact": "[email protected]" }
  ],
  "fiche_name": "Auto Sud - Marseille Centre"
}

Google listing

Google Business metrics

GET/api/v2/gmb/metricsAuthread

Returns the Google listing's KPIs over a period: views, searches, calls, website clicks, direction requests.

ParameterTypeDefault
period_daysinteger1-90 · 30
fiche_idstringDefault listing: optional fiche_id
JSON
{
  "fiche_id": "gmb-fiche-123",
  "fiche_name": "Auto Sud - Marseille Centre",
  "period_days": 30,
  "kpis": {
    "views_total": 5420,
    "views_search": 3110,
    "views_maps": 2310,
    "calls": 87,
    "website": 214,
    "directions": 156,
    "messages": 12,
    "bookings": 4
  },
  "timeseries": [
    { "label": "2026-06-01", "vues": 180, "interactions": 22 }
  ],
  "breakdown": {}
}

Optimization score

GET/api/v2/gmb/optimizerAuthread

Returns the listing's latest optimization score (0 to 100), cached. Fast read, no new audit.

JSON
{
  "kind": "gmb",
  "fiche_id": "gmb-fiche-123",
  "fiche_name": "Auto Sud - Marseille Centre",
  "display_name": "Auto Sud - Marseille Centre",
  "score": 82,
  "breakdown": {},
  "scored_at": "2026-07-05T08:00:00Z"
}

Run a full audit

POST/api/v2/gmb/optimizer/auditAuthpublish

Runs a full audit of the listing (Google data + AI analysis) and returns a detailed score with improvement suggestions. Costly operation: supports dry_run.

JSON
{
  "kind": "gmb",
  "fiche_id": "gmb-fiche-123",
  "fiche_name": "Auto Sud - Marseille Centre",
  "score": 82,
  "breakdown": {},
  "suggestions": [
    { "field": "description", "reason": "Trop courte", "suggested_value": "..." },
    { "field": "hours", "reason": "Horaires du dimanche manquants" }
  ],
  "review_count": 128,
  "avg_rating": 4.6,
  "analyzed_at": "2026-07-05T08:00:00Z"
}

Apply changes

POST/api/v2/gmb/optimizer/applyAuthpublish

Applies a list of {field, value} changes to the Google listing (real write to Google). Costly operation: supports dry_run.

Body (JSON)

JSON
{
  "changes": [
    { "field": "description", "value": "Garage automobile à Marseille..." }
  ],
  "dry_run": true
}

Response (dry_run):

JSON
{
  "dry_run": true,
  "fiche_id": "gmb-fiche-123",
  "fiche_name": "Auto Sud - Marseille Centre",
  "changes": [ { "field": "description", "value": "Garage automobile à Marseille..." } ],
  "fields": ["description"]
}

Local Pack visibility score

GET/api/v2/visibility/scanAuthread

Returns the listing's latest visibility scan in the Google Local Pack: score and average position around the location.

JSON
{
  "fiche_id": "gmb-fiche-123",
  "snapshot_id": "snap-789",
  "visibility_score": 74,
  "scanned_at": "2026-07-04T10:00:00Z"
}

If no scan is available, the response takes the following form — not the flat form with visibility_score:

JSON
{ "fiche_id": "gmb-fiche-123", "snapshot": null }

SEO & keywords

SEO data

GET/api/v2/seo/dataAuthread

Returns the listing's latest full SEO snapshot. The kind parameter selects the content: snapshot (positions, traffic, health) or competitors (identified competitors).

ParameterTypeDefault
kindstringsnapshot · competitors · (défaut : snapshot)
bulle_idstringRequired with a sphere key (the targeted location). Optional with a bubble key.

Response:

With data (the normal case), the response is flat:

JSON
{
  "kind": "snapshot",
  "snapshot_id": 42,
  "bulle_id": "abc-123",
  "created_at": "2026-03-10T04:00:00Z",
  "website_url": "https://auto-sud-marseille.fr",
  "has_ai_analysis": true,
  "ranked_keywords": {},
  "competitors": {},
  "onpage": {},
  "pagespeed": {},
  "ai_analysis": {}
}
⚠️

Conditional content blocks

The five content blocks — ranked_keywords, competitors, onpage, pagespeed, ai_analysis — are absent from the response when the scan didn't produce them: they aren't null, they simply aren't there. Test for their presence before reading them.

When no scan has run yet for this location:

JSON
{ "kind": "snapshot", "snapshot": null, "bulle_id": "abc-123" }

With kind=competitors:

JSON
{
  "kind": "competitors",
  "bulle_id": "abc-123",
  "competitors": [],
  "snapshot_date": "2026-03-10T04:00:00Z"
}

Without a scan: competitors is [] and snapshot_date is absent.

Tracked keywords

GET/api/v2/seo/tracked-keywordsAuthread

Lists the keywords tracked for the listing, with their current position, change (delta) and search volume.

bulle_idRequired with a sphere key (the targeted location). Optional with a bubble key.

JSON
[
  {
    "id": 12,
    "keyword": "garage marseille",
    "last_position": 4,
    "previous_position": 6,
    "delta": 2,
    "last_search_volume": 1300,
    "last_url": "https://autosud.fr/",
    "last_checked_at": "2026-07-05T06:00:00Z",
    "created_at": "2026-05-01T09:00:00Z",
    "history": [ { "checked_at": "2026-07-05T06:00:00Z", "position": 4 } ]
  }
]

Add a keyword to tracking

POST/api/v2/seo/tracked-keywordsAuthpublish

Adds a keyword to the listing's rank tracking.

Body (JSON)

JSON
{ "keyword": "garage marseille centre" }

bulle_idRequired with a sphere key: the targeted location. Inferred automatically with a bubble key.

Response:

JSON
{
  "id": 34,
  "keyword": "garage marseille centre",
  "last_position": null,
  "previous_position": null,
  "delta": null,
  "last_search_volume": null,
  "last_url": null,
  "last_checked_at": null,
  "created_at": "2026-07-20T10:00:00Z",
  "history": []
}

Search Console data

GET/api/v2/seo/search-consoleAuthread

Returns live Google Search Console data (impressions, clicks, position) by the requested dimensions, filters and dates.

ParameterTypeDefault
dimensionsarrayquery · page · date · country · device
daysinteger1-365
start_datestringISO 8601
end_datestringISO 8601
row_limitinteger-
query_containsstring-
query_equalsstring-
page_containsstring-
page_equalsstring-
countrystring-
devicestring-
search_typestring-
bulle_idstringRequired with a sphere key (the targeted location). Optional with a bubble key.

Response:

JSON
{
  "bulle_id": "abc-123",
  "site_url": "https://autosud.fr/",
  "start_date": "2026-06-01",
  "end_date": "2026-06-30",
  "dimensions": ["query"],
  "search_type": "web",
  "row_count": 1,
  "rows": [ { "keys": ["garage marseille"], "clicks": 42, "impressions": 800, "ctr": 0.0525, "position": 4.2 } ]
}

Run an SEO scan

POST/api/v2/seo/scanAuthpublish

Runs a full SEO scan of the listing. Subject to a monthly quota (429 if exceeded). Costly operation: supports dry_run.

Body (JSON)

JSON
{ "dry_run": false }

bulle_idRequired with a sphere key: the targeted location. Inferred automatically with a bubble key.

⚠️

429

Rate limit exceeded

Response:

JSON
{
  "ran": true,
  "kind": "full",
  "bulle_id": "abc-123",
  "scanned_at": "2026-07-20T10:00:00Z",
  "quota_used": 3,
  "quota_limit": 10
}

Look up a keyword

POST/api/v2/seo/keyword-lookupAuthpublish

Analyzes a keyword on demand: search volume, difficulty, related keywords. Costly operation: supports dry_run.

Body (JSON)

JSON
{ "keyword": "vidange voiture marseille", "dry_run": false }

bulle_idRequired with a sphere key: the targeted location. Inferred automatically with a bubble key.

Response:

JSON
{
  "kind": "keyword-lookup",
  "keyword": "vidange voiture marseille",
  "result": {}
}

SEO articles (create & publish)

Create articles

POST/api/v2/articlesAuthpublish

Creates one or more articles for connected sites (WordPress, Wix). AI writing runs in the background. Subject to a quota. 201 response.

Body (JSON)

JSON
{
  "title": "Comment entretenir sa voiture en été",
  "target_keyword": "entretien voiture été",
  "site_ids": [12, 34],
  "wix_site_ids": [7],
  "ai_word_count_target": 1500,
  "generate_content": true,
  "generate_image": false,
  "tone": "professional",
  "language": "fr",
  "meta_title": "Entretien voiture été | Auto Sud",
  "meta_description": "Nos conseils d'expert pour préparer l'été.",
  "excerpt": "Préparez votre véhicule pour l'été."
}

Parameters

FieldTypeRequired
titlestring (min 3)Yes
target_keywordstring (min 2)Yes
site_id · site_idsinteger · integer[]No
wix_site_id · wix_site_idsinteger · integer[]No
ai_word_count_targetinteger · 1500No
generate_contentboolean · trueNo
generate_imageboolean · falseNo
tonestring · professionalNo
languagestring · frNo
featured_image_url · meta_title · meta_description · excerptstringNo
bulle_idstringSphere key
💡

201 Created

Generation is asynchronous: the article is created first, then written in the background. Fetch its final content via GET /v2/articles/{id}.

Response:

JSON
{
  "created": [ { "id": 42, "title": "Comment entretenir sa voiture en été", "site_id": 12 } ],
  "skipped": [ { "wix_site_id": 7, "reason": "already_exists" } ],
  "count": 1,
  "generating": true
}
💡

Without a CMS (custom-built site)

By providing bulle_id instead of a CMS site (site_ids / wix_site_ids), the article is created and attached to the location, without a CMS. This is the mode for custom-built sites (Next.js, Astro, in-house theme): the article is then served by GET /api/v2/articles, which your site consumes itself.
JSON
{
  "title": "5 conseils pour entretenir sa voiture",
  "target_keyword": "entretien voiture marseille",
  "bulle_id": "abc-123"
}

The response then carries kind = api in created[], with site_id set to null:

JSON
{
  "created": [ { "id": 43, "kind": "api", "site_id": null } ],
  "count": 1,
  "generating": true
}

Update an article

PUT/api/v2/articles/{article_id}Authpublish

Partially updates an article (only the provided fields change). Returns 409 if the article is already published online.

Body (JSON)

JSON
{
  "title": "Nouveau titre",
  "content_html": "<h2>...</h2>",
  "excerpt": "...",
  "meta_title": "...",
  "meta_description": "...",
  "target_keyword": "...",
  "featured_image_url": "https://..."
}
💡

Partial update

All fields are optional: only the fields you send are updated, the rest stay unchanged.

Response:

JSON
{ "article_id": 42, "updated_fields": ["title", "meta_description"], "status": "draft" }

Publish or schedule an article

POST/api/v2/articles/{article_id}/publishAuthpublish

Publishes an article immediately, or schedules it if scheduled_at (future ISO date) is provided. Costly operation: supports dry_run.

Body (JSON)

JSON
{
  "scheduled_at": "2026-07-15T09:00:00+02:00",
  "dry_run": false
}

Response:

JSON
{
  "scheduled": true,
  "article_id": 42,
  "title": "Comment entretenir sa voiture en été",
  "scheduled_at": "2026-07-15T09:00:00+02:00"
}

QR codes

List QR codes

GET/api/v2/qr-codesAuthread

Returns the bubble's tracked QR codes, with scan count and redirect type (gate).

JSON
[
  {
    "id": 1,
    "bulle_id": "abc-123",
    "name": "Avis Auto Sud",
    "target": "Page Google Reviews",
    "scan_count": 342,
    "last_scanned_at": "2026-07-18T15:00:00Z",
    "is_active": true,
    "gate_enabled": true,
    "gate_threshold": 4,
    "created_at": "2026-04-01T10:00:00Z"
  }
]

bulle_idRestrict to one location. Default: the key's location, or every location of the brand.

Create a QR code

POST/api/v2/qr-codesAuthpublish

Creates a tracked QR code pointing to a target URL, with an optional color and logo.

Body (JSON)

JSON
{
  "target_url": "https://g.page/r/xxxx/review",
  "name": "Avis Auto Sud",
  "color_hex": "#000000",
  "with_logo": true
}

bulle_idOptional: without it, a sphere key creates a QR code at the brand level (valid).

Response:

JSON
{
  "id": 1,
  "name": "Avis Auto Sud",
  "target_url": "https://g.page/r/xxxx/review",
  "short_url": "https://gmb.link/xxxx",
  "png_url": "https://app.gmb-club.com/static/qr/xxxx.png",
  "color_hex": "#000000",
  "with_logo": true,
  "scan_count": 0,
  "created_at": "2026-07-20T10:00:00Z"
}

Sites & reports

Connected sites

GET/api/v2/sitesAuthread

Lists the sites connected to the bubble. Filterable by platform (wordpress, wix, shopify, prestashop).

ParameterTypeDefault
platformstringwordpress · wix
bulle_idstringRestrict to one location. Default: the key's location, or every location of the brand.
JSON
[
  {
    "platform": "wordpress",
    "id": 1,
    "site_url": "https://autosud.fr",
    "site_name": "Auto Sud",
    "sphere_id": "xyz-789",
    "bulle_id": "abc-123",
    "is_shared": false,
    "has_api_key": true,
    "wp_version": "6.5",
    "plugin_version": "1.4.0",
    "theme_name": "GeneratePress"
  },
  {
    "platform": "wix",
    "id": 2,
    "site_url": "https://autosud-aix.wixsite.com",
    "site_name": "Auto Sud Aix",
    "sphere_id": "xyz-789",
    "bulle_id": "abc-456",
    "is_shared": false,
    "has_token": true
  }
]

List reports

GET/api/v2/reportsAuthread

Returns already generated reports, with PDF and CSV download URLs.

ParameterTypeDefault
typestringfiche · (défaut : fiche)
limitinteger1-100 · 20
bulle_idstringRestrict to one location. Default: the key's location, or every location of the brand.
JSON
[
  {
    "fiche_name": "Auto Sud - Marseille Centre",
    "report_name": "Rapport juin 2026",
    "period": "2026-06",
    "generated_at": "2026-07-01T08:00:00Z",
    "pdf_url": "https://app.gmb-club.com/static/reports/rep-1.pdf",
    "csv_url": "https://app.gmb-club.com/static/reports/rep-1.csv"
  }
]

Generate a report

POST/api/v2/reportsAuthpublish

Generates a PDF report. type = fiche for one location, or combine with at least two fiche_ids. Asynchronous processing (~1 to 2 min): 202 response, result retrievable via GET /v2/reports.

Body (JSON)

JSON
{
  "type": "combine",
  "fiche_ids": ["gmb-fiche-123", "gmb-fiche-456"],
  "date_start": "2026-06-01",
  "date_end": "2026-06-30",
  "report_name": "Rapport juin 2026",
  "dry_run": false
}
💡

date_start and date_end required

The date_start and date_end fields (ISO format, YYYY-MM-DD) are required in the request body.
💡

202 Accepted

Long-running jobs (report generation, review requests, article generation) return a 202 code with an identifier. Fetch the result afterwards via the matching read endpoints (for example GET /v2/reports for a report, or GET /v2/articles for an article).

Response:

JSON
{ "generated": true, "type": "combine", "report_id": "rep-1", "fiches_count": 2 }

Images

Upload an image

POST/api/v2/images/uploadAuthpublish

Sends an image (image_base64 OR url, never both). JPEG, PNG, WebP, GIF formats, 8 MB maximum. Returns an image_url reusable in posts and articles.

Body (JSON)

JSON
{ "url": "https://example.com/photo.jpg" }
// ou : { "image_base64": "data:image/png;base64,iVBORw0..." }

Response:

JSON
{
  "uploaded": true,
  "image_url": "https://app.gmb-club.com/static/uploads/mcp/xxxx.webp",
  "size_bytes": 84213,
  "format": "webp"
}

Generate an image with AI

POST/api/v2/images/generateAuthpublish

Generates an image with AI from a prompt. align_with_brand (on by default) applies the brand's identity. Returns an image_url.

Body (JSON)

JSON
{
  "prompt": "Un mécanicien souriant dans un garage moderne et lumineux",
  "align_with_brand": true
}

bulle_idRequired with a sphere key when align_with_brand is true (the location whose brand guidelines are aligned).

Response:

JSON
{
  "generated": true,
  "image_url": "https://app.gmb-club.com/static/uploads/mcp/xxxx.webp",
  "brand_aligned": true
}

Permissions

Each API key has configurable permissions:

read

Read posts, articles, connections and info

schedule

Create and modify scheduled posts

publish

Publish immediately

delete

Delete posts

Rate Limiting

Each API key has configurable request limits:

LimitDefault value
Per minute60
Per day1000

Error codes

CodeDescription
200Success
201Resource created
202Long operations: 202 response
400Invalid request (check Content-Type and JSON format)
401Missing or invalid API key
403Insufficient permission
404Resource not found
409State conflict (e.g. article already published)
410Resource gone at the provider (e.g. review removed from Google)
422Invalid data
429Rate limit exceeded
500Server error
502External service failed (Google, DataForSEO)
503Service not configured or unavailable

Code examples

Python
import requests

API_KEY = "gmb_live_xxxx..."
BASE_URL = "https://app.gmb-club.com/api/v2"
headers = { "X-API-Key": API_KEY, "Content-Type": "application/json" }

# 1. Get key info
me = requests.get(f"{BASE_URL}/me", headers=headers).json()
print(f"Sphere: {me['sphere']['name']}")
print(f"Bubble: {me['bulle']['name']}")

# 2. Create a post
response = requests.post(f"{BASE_URL}/posts", headers=headers, json={
    "content": "Post from Python!",
    "platforms": ["facebook", "instagram"],
    "scheduled_at": "2025-12-01T10:00:00+01:00"
})
print(response.json())

# 3. Get published articles
articles = requests.get(f"{BASE_URL}/articles", headers=headers,
    params={"status": "published", "limit": 10}).json()
for article in articles['articles']:
    print(f"#{article['id']}: {article['title']}")

Make · n8n · Zapier

Make.com & n8n modules

The official Make.com and n8n modules (n8n-nodes-gmb-club) cover the main operations out of the box. For endpoints not yet exposed by a module, call the API directly: the "Make an API Call" module on Make, or the "HTTP Request" node with the GMB Club credential on n8n.
💡

dry_run

Tip: in your Make or n8n scenarios, set dry_run: true on costly operations to validate the call before running it for real.

Make.com setup

Use the HTTP → Make a request module.

🚫

Critical parameter

In Body type, select Raw then JSON (application/json). Do not leave on multipart/form-data.

Zapier setup

Use the Webhooks by Zapier → Custom Request action.

Security

MeasureDescription
Hashed keysStored as SHA256 hash, never in plain text
HTTPS requiredAll requests must use HTTPS
Bubble isolationEach key only accesses its bubble's data
Optional expirationKeys can have an expiration date
RevocationKeys can be revoked at any time
Rate limitingAbuse protection via Redis