Localranker API

Welcome to the Localranker API. You can use our API to access all available endpoints for managing locations, reviews, publications, and statistics.

The API is organized around REST. All requests should be made over SSL. All request and response bodies, including errors, are encoded in JSON.

Successful GET responses keep their historical shape (resource objects at the root). Error responses use a consistent envelope with success, error, and code — see Errors.

Base URL

https://developers.localranker.fr

All endpoints documented below are relative to this base URL.

Authentication

Authenticate your API requests by including your API key in the x-api-key header of every request.

Your API key carries privileges — keep it secret. Do not share it in publicly accessible areas such as GitHub or client-side code.

Header

Header Value
x-api-key Your secret API key (e.g. sk_live_...)

All authenticated endpoints will return 401 Unauthorized if the key is missing or invalid.

Example header
// Include in every request
headers: {
  "x-api-key": "sk_live_your_api_key"
}

Rate Limiting

The API enforces rate limits to ensure fair usage and availability for all users.

Limit Details
IP Rate Limit 100 requests per minute per IP address
API Credits Each request consumes 1 API credit. When credits are exhausted, the API returns 429 Too Many Requests.
Cached responses also consume 1 credit. Contact your account manager to increase your credit limit.

Errors

The API uses conventional HTTP status codes. Error bodies share a common envelope so you can branch on code while still reading error for display.

Error envelope

Field Description
success Always false on errors
error Human-readable message
code Machine-readable identifier (when applicable)
details / errors Same array on validation failures (legacy integrations may read errors)

HTTP status codes

Code Meaning Description
200 OK The request succeeded.
201 Created A new resource was created successfully.
400 Bad Request Invalid input. Check details / errors and code (often VALIDATION_ERROR).
401 Unauthorized Missing/invalid API key (MISSING_API_KEY, INVALID_API_KEY) or no access to the location (LOCATION_ACCESS_DENIED).
403 Forbidden IP blocked after repeated unauthenticated requests (IP_BLOCKED).
429 Too Many Requests Rate limit (RATE_LIMIT_EXCEEDED) or API credits exhausted (API_CREDITS_EXCEEDED).
500 Server Error Unexpected failure (INTERNAL_ERROR). Retry later.

Common code values

code Typical HTTP When
VALIDATION_ERROR 400 Invalid query/body (express-validator)
MISSING_API_KEY 401 No x-api-key header
INVALID_API_KEY 401 Unknown API key
MISSING_LOCATION_IDENTIFIER 400 PUT/POST location or media without locationId or placeId
LOCATION_ACCESS_DENIED 401 Location not linked to your account
API_CREDITS_EXCEEDED 429 No API credits remaining
RATE_LIMIT_EXCEEDED 429 IP or per-endpoint creation/update limits
MEDIA_UPLOAD_FAILED 400 Photo validation or GBP upload failure (see results)
CREATE_LOCATION_FAILED 400 Location could not be created
Validation error — 400
{
  "success": false,
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": [
    {
      "type": "field",
      "msg": "locationId must be a positive integer",
      "path": "locationId",
      "location": "query"
    }
  ],
  "errors": [ /* same as details */ ]
}
Missing API key — 401
{
  "success": false,
  "error": "No api key provided",
  "code": "MISSING_API_KEY"
}
API credits exhausted — 429
{
  "success": false,
  "error": "API credit exceeded",
  "code": "API_CREDITS_EXCEEDED"
}
IP rate limit — 429
{
  "success": false,
  "error": "Too many requests from this IP. Max: 100/min - Please try again after 1 minute.",
  "code": "RATE_LIMIT_EXCEEDED"
}
Media upload failure — 400
{
  "success": false,
  "message": "All photos failed validation",
  "error": "All photos failed validation",
  "code": "MEDIA_UPLOAD_FAILED",
  "results": [
    {
      "url": "https://example.com/photo.jpg",
      "status": "validation_error",
      "error": "Image too small"
    }
  ]
}

Locations

List locations

GET /v1/locations

Returns a paginated list of all locations (placeID, locationID, name, address) from your account. Results are paginated by 100.

As long as nextPage is present in the response, more pages are available. Once it disappears, you have reached the end.

Query Parameters

Param Type Required Description
page integer optional Page number (1-999). Defaults to 1.
Request
const response = await fetch("https://developers.localranker.fr/v1/locations?page=1", {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "locations": [
    {
      "placeID": "ChIJh5ixZOZz5kcRtmuVD2mF07w",
      "locationID": 102,
      "locationName": "Localranker",
      "locationAddress": "15 Rue des Halles, Paris, 75001"
    }
  ],
  "nextPage": "https://developers.localranker.fr/v1/locations?page=2",
  "totalItems": 150,
  "totalPages": 2
}

Opening hours

GET /v1/locations/opening-hours

Returns opening hours for a location. Mirrors Google Business Profile Opening Hours combined with GBP Special Hours.

Only open days are returned. Closed days are omitted. If the response is empty, we do not have hours in our systems for this location.

Query Parameters

Param Type Required Description
locationId integer required Location ID
Request
const response = await fetch("https://developers.localranker.fr/v1/locations/opening-hours?locationId=102", {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "locationId": 102,
  "hours": [
    {
      "openDay": "MONDAY",
      "openTime": { "hours": 9 },
      "closeDay": "MONDAY",
      "closeTime": { "hours": 18 }
    },
    {
      "openDay": "TUESDAY",
      "openTime": { "hours": 9 },
      "closeDay": "TUESDAY",
      "closeTime": { "hours": 18 }
    }
  ]
}

Get location

GET /v1/locations/location

Returns detailed information about a single location including title, opening hours, categories, services, address, and more.

Query Parameters

Param Type Required Description
locationId integer optional* Location ID
placeId string optional* Google Place ID
Either locationId or placeId must be provided, but not both.
Request
const response = await fetch("https://developers.localranker.fr/v1/locations/location?placeId=ChIJh5ixZOZz5kcRtmuVD2mF07w", {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "title": "Localranker",
  "languageCode": "fr",
  "phoneNumbers": {},
  "categories": {
    "primaryCategory": {
      "name": "categories/gcid:internet_marketing_service",
      "displayName": "Service de marketing Internet"
    }
  },
  "storefrontAddress": {
    "addressLines": ["15 Rue des Halles"],
    "locality": "Paris",
    "postalCode": "75001",
    "regionCode": "FR"
  },
  "websiteUri": "https://localranker.fr",
  "regularHours": { /* ... */ },
  "profile": {
    "description": "LocalRanker est une plateforme..."
  }
}

Create location

POST /v1/locations/location

Creates a new location on Google Business Profile. Only approved accounts can use this endpoint.

The location object should mirror Google's location API format.

Body Parameters

Param Type Required Description
gbpAccountId string required Google account ID (ask your account manager)
location object required Location data (title, categories, storefrontAddress, etc.)
isGoogle boolean required Create on Google Business Profile
isTestingMode boolean required If true, validates format only without creating. Does not consume credits.

Allowed location fields

Field Type Required Description
title string required Business name. Max 125 characters.
categories object required primaryCategory.name must be a categories/gcid:<id> resource name. Optional additionalCategories array (max 9, no duplicates, none equal to the primary).
storefrontAddress object conditional* Postal address. Only regionCode and addressLines (max 5) are mandatory; other fields (locality, postalCode, administrativeArea, languageCode, etc.) are optional.
serviceArea object conditional* For service-area businesses. businessType is CUSTOMER_LOCATION_ONLY or CUSTOMER_AND_BUSINESS_LOCATION; regionCode is required when CUSTOMER_LOCATION_ONLY; optional places.placeInfos array (max 20).
languageCode string optional BCP-47 language of the location. Immutable — set at creation only.
storeCode string optional External identifier, unique within the account.
phoneNumbers object optional primaryPhone (string) and additionalPhones (array, max 2)
websiteUri string optional Website URL (must start with http:// or https://)
regularHours object optional Opening hours (periods array). Times are 00:0024:00.
specialHours object optional Exceptional hours (specialHourPeriods array)
openInfo object optional Open status (OPEN, CLOSED_TEMPORARILY, CLOSED_PERMANENTLY) and openingDate
profile object optional Business description (max 750 characters)
serviceItems array optional Structured or free-form services. Free-form display names must be unique per category (case-insensitive).
labels array optional Private free-form tags (1–255 characters each).
latlng object optional latitude/longitude. Ignored when the address geocodes successfully; only approved clients can set the map pin.
* storefrontAddress is required for businesses with a physical storefront. It is optional (and ignored by Google) for service-area-only businesses — set serviceArea.businessType: "CUSTOMER_LOCATION_ONLY" with a serviceArea.regionCode instead.
Set isTestingMode: true first to validate your request format before creating a real location.
Rate limit: max 10 location creations per day per API key.
Request
const response = await fetch("https://developers.localranker.fr/v1/locations/location", {
  method: "POST",
  headers: {
    "x-api-key": "sk_live_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    isGoogle: true,
    isTestingMode: true,
    gbpAccountId: "123456789",
    location: {
      title: "My New Location",
      storefrontAddress: {
        addressLines: ["12 rue de la paix"],
        locality: "Paris",
        postalCode: "75001",
        regionCode: "FR"
      },
      categories: {
        primaryCategory: {
          name: "categories/gcid:internet_marketing_service"
        }
      }
    }
  })
});
Response — 201 Created
{
  "success": true,
  "data": {
    "message": "TEST MODE - Location created successfully",
    "requestId": "d08c141b-15d3-44ad-...",
    "gbpAccountId": "123456789"
  }
}

Upload media

POST /v1/locations/media

Uploads photos to a location on Google Business Profile. Supports 1 to 5 photos per request.

Body Parameters

Param Type Required Description
locationId integer optional* Location ID
placeId string optional* Google Place ID
photos array required Array of 1-5 photo objects with url and optional category
isTestingMode boolean optional If true, validates without uploading
Valid categories: COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, PRODUCT, AT_WORK, FOOD_AND_DRINK, MENU, COMMON_AREA, ROOMS, TEAMS, ADDITIONAL
Request
const response = await fetch("https://developers.localranker.fr/v1/locations/media", {
  method: "POST",
  headers: {
    "x-api-key": "sk_live_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    locationId: 102,
    photos: [
      { url: "https://example.com/photo1.jpg", category: "EXTERIOR" },
      { url: "https://example.com/photo2.jpg", category: "INTERIOR" }
    ]
  })
});
Response — 200 OK
{
  "success": true,
  "message": "All photos uploaded successfully",
  "results": [
    { "url": "https://example.com/photo1.jpg", "status": "success" },
    { "url": "https://example.com/photo2.jpg", "status": "success" }
  ]
}
Error response — 400
// See Errors — MEDIA_UPLOAD_FAILED; message + results preserved

Update location

PUT /v1/locations/location

Updates an existing location on Google Business Profile. Pass only the fields you want to update. Either location or attributes (or both) must be provided.

Body Parameters

Param Type Required Description
locationId integer optional* Location ID
placeId string optional* Google Place ID
location object optional** Location fields to update (mirrors GBP format)
attributes object optional** Attributes to update (e.g. social links)
isTestingMode boolean required If true, validates format only without applying changes. Does not consume credits.
* Either locationId or placeId must be provided in the request body (not only inside location). Returns MISSING_LOCATION_IDENTIFIER if both are absent.
** At least one of location or attributes must be provided.

Allowed location fields

Field Type Description
title string Business name. Max 125 characters.
storeCode string External store code identifier
phoneNumbers object primaryPhone (string) and additionalPhones (array, max 2)
categories object primaryCategory and optional additionalCategories array
storefrontAddress object Address with regionCode, addressLines, locality, postalCode, etc.
regularHours object Regular opening hours (periods array)
specialHours object Special / exceptional hours (specialHourPeriods array)
openInfo object Open status (OPEN, CLOSED_TEMPORARILY, CLOSED_PERMANENTLY), canReopen, openingDate
latlng object latitude and longitude (numbers). Read-only in practice: Google only lets approved clients set the map pin, and ignores it when the address geocodes. Accepted for compatibility but not applied.
websiteUri string Website URL (must start with http:// or https://)
profile object Business description (max 750 characters)
serviceItems array Service items with optional pricing and structured or free-form definitions. Free-form display names must be unique per category (case-insensitive).
Updating an address: send a complete storefrontAddress (regionCode is required) and let Google geocode it. Do not send latlng — the map pin can only be set by Google-approved clients and is otherwise ignored. Always dry-run first with isTestingMode: true. If Google returns PIN_DROP_REQUIRED, the address could not be geocoded precisely: complete/correct it, or drop the pin manually in the Google Business Profile UI, then retry.

Allowed attributes fields

Field Type Description
socials array Array of { type, url } objects. Valid types: url_facebook, url_instagram, url_tiktok, url_youtube, url_linkedin, url_pinterest, url_twitter, url_whatsapp, url_appointment
Rate limit: max 500 updates per hour per API key.
Request — Update basic fields
const response = await fetch("https://developers.localranker.fr/v1/locations/location", {
  method: "PUT",
  headers: {
    "x-api-key": "sk_live_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    locationId: 102,
    isTestingMode: false,
    location: {
      title: "Updated Location Name",
      websiteUri: "https://newwebsite.fr"
    }
  })
});
Request — Update address (no latlng; dry-run first)
const response = await fetch("https://developers.localranker.fr/v1/locations/location", {
  method: "PUT",
  headers: {
    "x-api-key": "sk_live_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    locationId: 102,
    // set false to apply once the dry-run passes
    isTestingMode: true,
    location: {
      storefrontAddress: {
        regionCode: "FR",
        addressLines: ["20 rue de Rivoli"],
        locality: "Paris",
        postalCode: "75004"
      }
    }
  })
});
Request — Update social links
const response = await fetch("https://developers.localranker.fr/v1/locations/location", {
  method: "PUT",
  headers: {
    "x-api-key": "sk_live_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    locationId: 102,
    isTestingMode: false,
    attributes: {
      socials: [
        { type: "url_instagram", url: "https://instagram.com/mybusiness" },
        { type: "url_facebook", url: "https://facebook.com/mybusiness" }
      ]
    }
  })
});
Response — 200 OK
{
  "message": "Request received and is processing",
  "requestId": 42
}

Publications

Get publications

GET /v1/publications

Returns the last 10 Google Posts for a given location.

Query Parameters

Param Type Required Description
locationId integer required Location ID
Request
const response = await fetch("https://developers.localranker.fr/v1/publications?locationId=102", {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "posts": [
    {
      "title": "New blog post about local SEO",
      "content": "Discover our latest tips...",
      "imageUrl": "https://...",
      "publishedAt": "2025-03-01T10:00:00.000Z"
    }
  ],
  "totalPostsReturned": 1
}

Statistics

Get statistics

GET /v1/stats

Returns review and performance statistics for your locations over a date range, including review counts, average ratings, response rates, and interval breakdowns.

Query Parameters

Param Type Required Description
locationsIds string required "all" or comma-separated location IDs (e.g. "102,132")
startDate string required Start date in YYYY-MM-DD format
endDate string required End date in YYYY-MM-DD format
interval string required One of: day, week, month, year
Request
const url = "https://developers.localranker.fr/v1/stats"
  + "?locationsIds=all&startDate=2025-01-01&endDate=2025-03-01&interval=month";

const response = await fetch(url, {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "reviews": {
    "reviews_count": 87,
    "nbReplied": 72,
    "responseRate": 83,
    "averageRate": 4.52
  },
  "report": { /* GBP performance stats */ }
}

Get GBP search keywords

GET /v1/stats/gbp-keywords

Returns the top Google Business Profile search keywords (the terms people searched for on Google to find your listings) with their monthly impressions, over a date range. Data is provided at month granularity.

Threshold values: Google sometimes returns a floor (a minimum impression count) instead of an exact number for low-volume keywords. When that happens, isThreshold is true and impressions represents that floor.

Query Parameters

Param Type Required Description
locationsIds string required "all" or comma-separated location IDs (e.g. "102,132")
startDate string required Start date in YYYY-MM-DD format (converted to month)
endDate string required End date in YYYY-MM-DD format (converted to month)
limit integer optional Max keywords per bucket (1-100). Default: 20
groupBy string optional account (default, aggregated across all locations) or location (per-location breakdown)
Request
const url = "https://developers.localranker.fr/v1/stats/gbp-keywords"
  + "?locationsIds=all&startDate=2025-01-01&endDate=2025-03-01&groupBy=account";

const response = await fetch(url, {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response (groupBy=account) — 200 OK
{
  "groupBy": "account",
  "searchKeywords": {
    "topKeywords": [
      { "keyword": "plombier paris", "impressions": 842, "isThreshold": false }
    ],
    "totalImpressions": 12500,
    "uniqueKeywords": 87,
    "monthlyTotals": [
      { "month": "2025-01", "impressions": 4100 }
    ]
  }
}
Response (groupBy=location) — 200 OK
{
  "groupBy": "location",
  "locations": [
    {
      "locationId": 102,
      "searchKeywords": { /* same shape as account.searchKeywords */ }
    }
  ]
}

Local Audit

Get local audit

GET /v1/local-audit

Returns the Local Score and the latest local audit detail for your locations: optimized criteria (valid) and detected problems (issues), each grouped by criticality (critical / medium / low) with both French and English labels.

Freshness: the audit reflects the latest computed run for each location (scheduled refresh + manual refreshes), not a real-time recomputation. Locations never audited yet are returned with audited: false and empty issue lists.
Bulk exports: for many locations (e.g. locationsIds=all), set includeCriteria=false to receive only scores and criticality counts without the full criterion text. Add includeHistory=true when you also need the 12-month score series.

Query Parameters

Param Type Required Description
locationsIds string required "all" or comma-separated location IDs (e.g. "102,132")
lang string optional fr, en, or all (default all — returns both languages)
includeHistory boolean optional When true, appends localScoreHistory: the last 12 calendar months of localScore per location (last audit of each month; null when no audit exists for that month). Default: false.
includeCriteria boolean optional When true (default), returns the full issues and valid lists with FR/EN labels per criterion. When false, returns only scores and criticality summaries (issuesSummary, validSummary) — recommended for bulk exports (e.g. locationsIds=all in Looker Studio).
Request
const url = "https://developers.localranker.fr/v1/local-audit"
  + "?locationsIds=102&lang=all&includeHistory=true";

const response = await fetch(url, {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Response — 200 OK
{
  "lang": "all",
  "locations": [
    {
      "locationId": 102,
      "placeId": "ChIJ...",
      "localScore": 78,
      "validCount": 22,
      "invalidCount": 8,
      "auditedAt": "2026-06-20T03:00:00.000Z",
      "audited": true,
      "issuesSummary": { "critical": 2, "medium": 4, "low": 2 },
      "validSummary": { "critical": 6, "medium": 12, "low": 4 },
      "issues": {
        "critical": [
          {
            "code": "no-website",
            "status": "issue",
            "criticality": "critical",
            "type": "basic-info",
            "labels": {
              "fr": { "header": "Aucun site web renseigné", "content": "...", "fix": "..." },
              "en": { "header": "No website filled in", "content": "...", "fix": "..." }
            }
          }
        ],
        "medium": [],
        "low": []
      },
      "valid": { /* same item shape, grouped by criticality */ },
      "localScoreHistory": [
        { "month": "2025-07", "localScore": 70 },
        { "month": "2025-08", "localScore": 74 },
        { "month": "2025-09", "localScore": null }
      ]
    }
  ]
}

Reviews

Get reviews

GET /v1/reviews

Returns a paginated list of reviews with powerful filtering options. Supports Google, Facebook, and Trustpilot reviews.

If neither locationId nor placeId is provided, returns reviews for all locations in your account.

Ideal for building custom review widgets, exporting review data, or integrating reviews into your own applications.

Best practice: If you are building a custom widget or any integration that repeatedly fetches the same data, implement a caching layer on your side (e.g. cache responses for a few minutes or hours). This prevents unnecessary API calls and helps you preserve your API credits.

Query Parameters

Param Type Required Description
locationId integer optional Filter by location ID
placeId string optional Filter by Google Place ID
page integer optional Page number. Default: 1
limit integer optional Results per page (1-100). Default: 20
platform string optional Filter by platform: google, facebook, trustpilot, or all. Default: all
startDate string optional Filter reviews from this date (YYYY-MM-DD)
endDate string optional Filter reviews until this date (YYYY-MM-DD)
minRating integer optional Minimum rating (1-5)
maxRating integer optional Maximum rating (1-5)
sortBy string optional Sort field: review_date or rating. Default: review_date
sortOrder string optional Sort direction: asc or desc. Default: desc
includeAnswer boolean optional Include owner reply fields. Default: true
hasAnswer boolean optional Filter: only answered (true) or unanswered (false) reviews
page × limit must not exceed 10,000.
Request — All reviews for a location
const response = await fetch("https://developers.localranker.fr/v1/reviews?locationId=102&limit=20", {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
const data = await response.json();
Request — Filtered (4-5 stars, Google only, 2025)
const url = "https://developers.localranker.fr/v1/reviews"
  + "?platform=google&minRating=4&startDate=2025-01-01&endDate=2025-12-31"
  + "&sortBy=rating&sortOrder=desc&includeAnswer=false";

const response = await fetch(url, {
  headers: { "x-api-key": "sk_live_your_api_key" }
});
Response — 200 OK
{
  "reviews": [
    {
      "reviewId": "AbCdEf123...",
      "platform": "google",
      "author": "Jean Dupont",
      "authorPhoto": "https://lh3.googleusercontent.com/...",
      "rating": 5,
      "content": "Excellent service, highly recommended!",
      "reviewDate": "2025-06-15T00:00:00.000Z",
      "mediaUrls": [],
      "locationName": "Localranker",
      "locationAddress": "15 Rue des Halles, Paris, 75001",
      "placeId": "ChIJh5ixZOZz5kcRtmuVD2mF07w",
      "locationId": 102,
      "ownerAnswer": "Merci beaucoup Jean!",
      "ownerAnswerDate": "2025-06-16T00:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "totalItems": 150,
    "totalPages": 8,
    "nextPage": "https://developers.localranker.fr/v1/reviews?page=2&limit=20"
  }
}