{
  "openapi": "3.0.3",
  "info": {
    "title": "GeoGridAPI",
    "version": "1.0.0",
    "description": "Geo-grid rank tracking API for Google Maps (local SEO). Send a Google Business Profile and a keyword: GeoGridAPI measures the listing's rank in the Google Maps results at every point of a grid around it (square 3×3 to 9×9, or circle, diamond, hexagon, cross, ellipse, rectangle), and returns the rank matrix, the competitors ranking ahead, the change since the previous grid and a PNG heatmap. 1 credit = 1 grid point. Test keys (gga_test_) return free mock grids.",
    "termsOfService": "https://geogridapi.io/en/legal/terms",
    "contact": {
      "name": "GeoGridAPI",
      "url": "https://geogridapi.io",
      "email": "sav@localnord.fr"
    }
  },
  "externalDocs": {
    "description": "Documentation",
    "url": "https://geogridapi.io/en/docs"
  },
  "servers": [
    {
      "url": "https://api.geogridapi.io"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Listings",
      "description": "Find a Google Business Profile."
    },
    {
      "name": "Grids",
      "description": "Run and read geo-grids."
    },
    {
      "name": "Batches",
      "description": "Several grids in one call."
    },
    {
      "name": "Schedules",
      "description": "Repeated grids."
    },
    {
      "name": "Account",
      "description": "Credits."
    }
  ],
  "paths": {
    "/v1/places/search": {
      "get": {
        "operationId": "searchPlaces",
        "tags": [
          "Listings"
        ],
        "summary": "Find a Google Business Profile",
        "description": "Search a listing by name and city to get its place_id. Free.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 200
            },
            "example": "Joe's Pizza New York"
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "en"
            },
            "example": "en"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching listings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Engine temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grids": {
      "post": {
        "operationId": "createGrid",
        "tags": [
          "Grids"
        ],
        "summary": "Run a geo-grid",
        "description": "Measures the Google Maps rank of a listing for a keyword at every point of a grid around it. Answers right away (202, status pending); the grid completes in 20 seconds to 2 minutes. Read it with GET /v1/grids/{id} or receive it on webhook_url. 1 credit per grid point.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key returns the grid already created, with no new charge."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGridRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Grid created, status pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grid"
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay of a grid already created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grid"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Not enough credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No listing matches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reused with other parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Engine temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listGrids",
        "tags": [
          "Grids"
        ],
        "summary": "List grids",
        "description": "Your grids, newest first, without point details.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "next_before of the previous page."
          },
          {
            "name": "place_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact keyword, case-insensitive."
          }
        ],
        "responses": {
          "200": {
            "description": "Grids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GridList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grids/estimate": {
      "post": {
        "operationId": "estimateGrid",
        "tags": [
          "Grids"
        ],
        "summary": "Exact price of a grid",
        "description": "Same body as POST /v1/grids. Returns the number of points and credits, without reserving or charging anything. Free.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGridRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No listing matches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grids/{id}": {
      "get": {
        "operationId": "getGrid",
        "tags": [
          "Grids"
        ],
        "summary": "Get a grid",
        "description": "Status and result of a grid: ranks at every point, summary, competitors ahead and change since the previous grid.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Grid id."
          }
        ],
        "responses": {
          "200": {
            "description": "The grid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grid"
                },
                "example": {
                  "id": "3f2c9b1e-6a7d-4c1e-9f0a-2b8d7e6c5a41",
                  "object": "grid",
                  "status": "completed",
                  "mode": "live",
                  "place": {
                    "place_id": "ChIJ…",
                    "name": "Joe's Pizza",
                    "address": "7 Carmine St, New York",
                    "lat": 40.7305,
                    "lng": -74.0021
                  },
                  "keyword": "pizza",
                  "shape": "square",
                  "size": 7,
                  "rows": 7,
                  "cols": 7,
                  "radius_km": 3,
                  "points_total": 49,
                  "summary": {
                    "average_rank": 4.3,
                    "average_rank_with_misses": 4.6,
                    "top3_points": 28,
                    "found_points": 48
                  },
                  "ranks": [
                    [
                      4,
                      3,
                      2,
                      2,
                      3,
                      5,
                      8
                    ],
                    [
                      3,
                      2,
                      1,
                      1,
                      2,
                      4,
                      7
                    ],
                    [
                      2,
                      1,
                      1,
                      1,
                      1,
                      3,
                      6
                    ],
                    [
                      2,
                      1,
                      1,
                      1,
                      1,
                      2,
                      5
                    ],
                    [
                      3,
                      2,
                      1,
                      1,
                      2,
                      3,
                      7
                    ],
                    [
                      6,
                      4,
                      3,
                      2,
                      3,
                      6,
                      12
                    ],
                    [
                      11,
                      8,
                      6,
                      5,
                      9,
                      15,
                      null
                    ]
                  ],
                  "created_at": "2026-10-16T10:00:00Z",
                  "completed_at": "2026-10-16T10:00:41Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown grid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grids/{id}/image": {
      "get": {
        "operationId": "getGridImage",
        "tags": [
          "Grids"
        ],
        "summary": "Heatmap image of a grid",
        "description": "PNG image (1200 × 1092) of a completed grid: map, rank dots, keyword, listing, date and key figures. Free.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Grid id."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "fr"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG image.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown grid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Grid still running (grid_not_ready) or failed (grid_failed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/history": {
      "get": {
        "operationId": "getHistory",
        "tags": [
          "Grids"
        ],
        "summary": "Ranking trend of a listing",
        "description": "Every completed grid of the same map, oldest first, and the change between the first and the last. Name the map with grid_id, or with place_id and keyword. Free.",
        "parameters": [
          {
            "name": "grid_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "place_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trend.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/History"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No grid for this map.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/batches": {
      "post": {
        "operationId": "createBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Run up to 50 grids",
        "description": "One listing with several keywords, or several listings with one keyword, with the same format. Each grid is charged when it starts and refunded if it fails.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. The same key returns the grid already created, with no new charge."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBatchRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Batch"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Not enough credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/batches/{id}": {
      "get": {
        "operationId": "getBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Get a batch",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Batch"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/schedules": {
      "post": {
        "operationId": "createSchedule",
        "tags": [
          "Schedules"
        ],
        "summary": "Repeat a grid every week or month",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown grid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Grid already repeated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listSchedules",
        "tags": [
          "Schedules"
        ],
        "summary": "List repeated grids",
        "responses": {
          "200": {
            "description": "Schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}": {
      "patch": {
        "operationId": "updateSchedule",
        "tags": [
          "Schedules"
        ],
        "summary": "Pause or resume a repeated grid",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteSchedule",
        "tags": [
          "Schedules"
        ],
        "summary": "Stop a repeated grid",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Account"
        ],
        "summary": "Credits balance",
        "responses": {
          "200": {
            "description": "Balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "gga_live_… or gga_test_… key from the console."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "invalid_body"
              },
              "message": {
                "type": "string",
                "example": "keyword: Too small: expected string to have >=1 characters"
              }
            },
            "additionalProperties": true
          }
        }
      },
      "Place": {
        "type": "object",
        "properties": {
          "place_id": {
            "type": "string",
            "example": "ChIJN1t_tDeuEmsRUsoyG83frY4"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "lat": {
            "type": "number",
            "example": 50.6292
          },
          "lng": {
            "type": "number",
            "example": 3.0573
          }
        }
      },
      "Center": {
        "type": "object",
        "required": [
          "lat",
          "lng"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          }
        },
        "additionalProperties": false
      },
      "GridFormat": {
        "type": "object",
        "properties": {
          "shape": {
            "type": "string",
            "enum": [
              "square",
              "circle",
              "diamond",
              "hexagon",
              "cross",
              "ellipse",
              "rectangle"
            ],
            "default": "square",
            "description": "Grid shape."
          },
          "size": {
            "type": "integer",
            "enum": [
              3,
              5,
              7,
              9
            ],
            "description": "Square only (default 7): side of the grid. Cost: size × size points. Not allowed with another shape."
          },
          "density": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ],
            "description": "Other shapes only (default medium). Points: circle 13/29/49, diamond 13/25/41, hexagon 17/27/47, cross 21/33/45, ellipse 19/35/45, rectangle 15/35/45."
          },
          "radius_km": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 30,
            "description": "Distance from center to edge. Square: 0.5 to 30 km (defaults 1.5 / 2.5 / 3.5 / 4.5 km by size). Other shapes: 1 to 30 km."
          },
          "language": {
            "type": "string",
            "pattern": "^[a-zA-Z]{2}$",
            "description": "Search language (2-letter code). Defaults to the account language.",
            "example": "en"
          },
          "country": {
            "type": "string",
            "pattern": "^[a-zA-Z]{2}$",
            "description": "Search country (2-letter ISO code), optional.",
            "example": "US"
          },
          "webhook_url": {
            "type": "string",
            "maxLength": 500,
            "description": "Public HTTPS URL called with the grid when it ends (grid.completed or grid.failed)."
          }
        }
      },
      "GridTarget": {
        "type": "object",
        "required": [
          "keyword"
        ],
        "properties": {
          "place_id": {
            "type": "string",
            "minLength": 3,
            "maxLength": 256,
            "description": "Google place id of the listing (ChIJ…). Send place_id or query, never both."
          },
          "query": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200,
            "description": "Listing name and city; the first match is measured. Send place_id or query, never both."
          },
          "center": {
            "$ref": "#/components/schemas/Center"
          },
          "keyword": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Keyword searched on Google Maps.",
            "example": "plumber"
          }
        }
      },
      "CreateGridRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/GridTarget"
          },
          {
            "$ref": "#/components/schemas/GridFormat"
          }
        ],
        "example": {
          "query": "Joe's Pizza, New York",
          "keyword": "pizza",
          "size": 7,
          "radius_km": 3
        }
      },
      "GridSummary": {
        "type": "object",
        "nullable": true,
        "properties": {
          "average_rank": {
            "type": "number",
            "nullable": true,
            "description": "Mean rank over the points where the listing appears."
          },
          "average_rank_with_misses": {
            "type": "number",
            "nullable": true,
            "description": "Mean rank with misses counted as 21."
          },
          "top3_points": {
            "type": "integer"
          },
          "found_points": {
            "type": "integer"
          }
        }
      },
      "TopListing": {
        "type": "object",
        "properties": {
          "rank": {
            "type": "integer"
          },
          "place_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "GridPoint": {
        "type": "object",
        "properties": {
          "row": {
            "type": "integer",
            "description": "0 = north."
          },
          "col": {
            "type": "integer",
            "description": "0 = west."
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "rank": {
            "type": "integer",
            "nullable": true,
            "description": "Position in the Google Maps results at this point; null = not in the top 20."
          },
          "top": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TopListing"
            },
            "description": "Top 3 listings at this point."
          }
        }
      },
      "Competitor": {
        "type": "object",
        "properties": {
          "place_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "top3_points": {
            "type": "integer",
            "description": "Points where it is in the top 3."
          },
          "first_points": {
            "type": "integer",
            "description": "Points where it ranks first."
          },
          "ahead_points": {
            "type": "integer",
            "description": "Points where it ranks ahead of your listing."
          }
        }
      },
      "PreviousGrid": {
        "type": "object",
        "nullable": true,
        "description": "Change since the previous grid of the same map (same listing, keyword, format, radius and center).",
        "properties": {
          "grid_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "average_rank": {
            "type": "number",
            "nullable": true
          },
          "top3_points": {
            "type": "integer"
          },
          "found_points": {
            "type": "integer"
          },
          "changes": {
            "type": "object",
            "properties": {
              "average_rank": {
                "type": "number",
                "nullable": true,
                "description": "Negative = better."
              },
              "top3_points": {
                "type": "integer"
              },
              "found_points": {
                "type": "integer"
              }
            }
          },
          "ranks": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "rank_changes": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "integer",
                "nullable": true
              }
            },
            "description": "Places gained per cell (positive = better, a miss counts as 21)."
          },
          "points": {
            "type": "object",
            "properties": {
              "improved": {
                "type": "integer"
              },
              "worsened": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Grid": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "grid"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "mcp",
              "console"
            ]
          },
          "api_key_id": {
            "type": "string",
            "nullable": true
          },
          "place": {
            "$ref": "#/components/schemas/Place"
          },
          "keyword": {
            "type": "string"
          },
          "shape": {
            "type": "string"
          },
          "density": {
            "type": "string",
            "nullable": true
          },
          "size": {
            "type": "integer"
          },
          "rows": {
            "type": "integer"
          },
          "cols": {
            "type": "integer"
          },
          "radius_km": {
            "type": "number"
          },
          "language": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "points_total": {
            "type": "integer",
            "description": "Grid points measured (points at sea are neither measured nor charged)."
          },
          "points_charged": {
            "type": "integer",
            "description": "Credits charged to the GeoGridAPI balance (0 with a test key or through RapidAPI)."
          },
          "summary": {
            "$ref": "#/components/schemas/GridSummary"
          },
          "ranks": {
            "type": "array",
            "nullable": true,
            "description": "rows × cols matrix, row 0 = north, column 0 = west. null = not in the top 20 (or outside the shape).",
            "items": {
              "type": "array",
              "items": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "points": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/GridPoint"
            }
          },
          "competitors": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/Competitor"
            },
            "description": "Who ranks ahead: the top 3 listings summed over the map."
          },
          "previous": {
            "$ref": "#/components/schemas/PreviousGrid"
          },
          "error": {
            "$ref": "#/components/schemas/GridError"
          },
          "webhook": {
            "type": "object",
            "nullable": true,
            "description": "Delivery of the webhook_url, when one was given.",
            "properties": {
              "status": {
                "type": "string",
                "nullable": true
              },
              "attempts": {
                "type": "integer"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "GridList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Grid"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_before": {
            "type": "string",
            "nullable": true,
            "description": "Pass as before to read the next page."
          }
        }
      },
      "PlaceList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "place_id": {
                  "type": "string",
                  "example": "ChIJN1t_tDeuEmsRUsoyG83frY4"
                },
                "name": {
                  "type": "string",
                  "nullable": true
                },
                "address": {
                  "type": "string",
                  "nullable": true
                },
                "lat": {
                  "type": "number",
                  "nullable": true,
                  "description": "null when Google gives no public location (service-area business)."
                },
                "lng": {
                  "type": "number",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "Estimate": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "estimate"
            ]
          },
          "shape": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "density": {
            "type": "string",
            "nullable": true
          },
          "rows": {
            "type": "integer"
          },
          "cols": {
            "type": "integer"
          },
          "radius_km": {
            "type": "number"
          },
          "points": {
            "type": "integer",
            "description": "Points that will be measured (points at sea removed)."
          },
          "points_at_sea": {
            "type": "integer"
          },
          "exact": {
            "type": "boolean",
            "description": "false: the listing could not be located, points is an upper bound."
          },
          "credits": {
            "type": "integer",
            "description": "Credits (or RapidAPI Points) the grid will cost."
          },
          "balance": {
            "type": "integer",
            "description": "Credits balance (not returned through RapidAPI)."
          },
          "balance_after": {
            "type": "integer",
            "description": "Not returned through RapidAPI."
          },
          "enough": {
            "type": "boolean",
            "description": "Not returned through RapidAPI."
          },
          "place": {
            "type": "object",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/Place"
              }
            ],
            "description": "Located listing (other shapes only), else null."
          }
        }
      },
      "HistoryEntry": {
        "type": "object",
        "properties": {
          "grid_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "average_rank": {
            "type": "number",
            "nullable": true
          },
          "top3_points": {
            "type": "integer"
          },
          "found_points": {
            "type": "integer"
          },
          "points_total": {
            "type": "integer"
          }
        }
      },
      "History": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "history"
            ]
          },
          "reference_grid_id": {
            "type": "string",
            "format": "uuid"
          },
          "mode": {
            "type": "string"
          },
          "place": {
            "$ref": "#/components/schemas/PlaceRef"
          },
          "keyword": {
            "type": "string"
          },
          "shape": {
            "type": "string"
          },
          "density": {
            "type": "string",
            "nullable": true
          },
          "size": {
            "type": "integer"
          },
          "rows": {
            "type": "integer"
          },
          "cols": {
            "type": "integer"
          },
          "radius_km": {
            "type": "number"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HistoryEntry"
            },
            "description": "Completed grids of the same map, oldest first."
          },
          "has_more": {
            "type": "boolean"
          },
          "change": {
            "type": "object",
            "nullable": true,
            "properties": {
              "since": {
                "type": "string",
                "format": "date-time"
              },
              "average_rank": {
                "type": "number",
                "nullable": true,
                "description": "Negative = better."
              },
              "top3_points": {
                "type": "integer"
              },
              "found_points": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "balance"
            ]
          },
          "points": {
            "type": "integer"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          }
        }
      },
      "CreateBatchRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/GridFormat"
          },
          {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "minItems": 1,
                "maxItems": 50,
                "items": {
                  "$ref": "#/components/schemas/GridTarget"
                }
              }
            }
          }
        ],
        "example": {
          "size": 7,
          "items": [
            {
              "query": "Joe's Pizza, New York",
              "keyword": "pizza"
            },
            {
              "query": "Joe's Pizza, New York",
              "keyword": "pizza delivery"
            }
          ]
        }
      },
      "BatchItem": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer"
          },
          "keyword": {
            "type": "string"
          },
          "query": {
            "type": "string",
            "nullable": true
          },
          "place": {
            "type": "object",
            "nullable": true,
            "properties": {
              "place_id": {
                "type": "string"
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "address": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "pending",
              "running",
              "completed",
              "failed"
            ]
          },
          "grid_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "points_total": {
            "type": "integer",
            "nullable": true
          },
          "points_charged": {
            "type": "integer"
          },
          "summary": {
            "$ref": "#/components/schemas/GridSummary"
          },
          "error": {
            "$ref": "#/components/schemas/GridError"
          }
        }
      },
      "Batch": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "batch"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed"
            ]
          },
          "mode": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "mcp",
              "console"
            ]
          },
          "shape": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "density": {
            "type": "string",
            "nullable": true
          },
          "radius_km": {
            "type": "number"
          },
          "items_count": {
            "type": "integer"
          },
          "counts": {
            "type": "object",
            "properties": {
              "queued": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              },
              "running": {
                "type": "integer"
              },
              "completed": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              }
            }
          },
          "credits_estimated": {
            "type": "integer"
          },
          "credits_charged": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchItem"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "launched_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "CreateScheduleRequest": {
        "type": "object",
        "required": [
          "grid_id",
          "frequency"
        ],
        "properties": {
          "grid_id": {
            "type": "string",
            "format": "uuid",
            "description": "An existing completed grid to repeat."
          },
          "frequency": {
            "type": "string",
            "enum": [
              "weekly",
              "monthly"
            ]
          },
          "weekday": {
            "type": "integer",
            "minimum": 1,
            "maximum": 7,
            "description": "Weekly: 1 = Monday … 7 = Sunday (default 1)."
          },
          "month_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28,
            "description": "Monthly: day of the month (default 1)."
          }
        },
        "additionalProperties": false
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "schedule"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "stopped"
            ]
          },
          "stopped_reason": {
            "type": "string",
            "nullable": true
          },
          "mode": {
            "type": "string"
          },
          "frequency": {
            "type": "string",
            "enum": [
              "weekly",
              "monthly"
            ]
          },
          "weekday": {
            "type": "integer",
            "nullable": true
          },
          "month_day": {
            "type": "integer",
            "nullable": true
          },
          "place": {
            "type": "object",
            "description": "The listing and the grid center.",
            "properties": {
              "place_id": {
                "type": "string"
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            }
          },
          "keyword": {
            "type": "string"
          },
          "shape": {
            "type": "string"
          },
          "density": {
            "type": "string",
            "nullable": true
          },
          "size": {
            "type": "integer"
          },
          "radius_km": {
            "type": "number"
          },
          "points_per_run": {
            "type": "integer"
          },
          "credits_per_month": {
            "type": "integer"
          },
          "next_run_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_run_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_grid_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "source_grid_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "runs_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GridError": {
        "type": "object",
        "nullable": true,
        "description": "Why a grid (or a batch item) failed; null otherwise.",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "place_not_found"
          },
          "message": {
            "type": "string",
            "example": "No Google Business Profile matches this place, or it has no public address."
          }
        }
      },
      "PlaceRef": {
        "type": "object",
        "properties": {
          "place_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "type": "string",
            "nullable": true
          }
        }
      }
    }
  }
}