Base URLs & Authentication

Base URLs
• API URL:  https://gtrack-api.wiremo.co
• MCP URL:  https://gtrack-mcp.wiremo.co/?key=gtrk_live_XXXXXXXXXXXXXXXXX
Authentication

All endpoints require an API key header:

x-api-key: gtrk_live_XXXXXXXXXXXXXXXXX
scans:write - contact support for this elevated permission.
Rate Limits
PlanReads/HourWrites/Hour
Business20001000
Pro50002000
Rate Limit Headers
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset HTTP 429 is returned when limits are exceeded.

POST Initiate Scan – No Polling (with Center)

/api/initiate-scan

Creates a new scan using a center coordinate. Grid coordinates are auto-generated.

scans:write
ParameterTypeRequiredDescription
centerArray [lat, lng]OptionalCenter coordinate for grid generation
coordinatesArray of [lat, lng]OptionalManual coordinates (use either center OR coordinates)
gridSizeIntegerRequiredGrid dimension (3-13, e.g., 3 = 3x3 = 9 points)
distanceNumberRequiredDistance from center (0.1+ km/miles)
unitStringRequired"kilometer" or "mile"
scanTypeStringOptional"maps" (1 credit) or "local" (2 credits), defaults to "maps"
keywordStringRequiredSearch keyword
placeIdStringRequiredGoogle Place ID
customZoomIntegerOptionalZoom level 1-20 (default: 13, maps only)
languageStringOptionalISO language code (default: 'en')
Important: Provide either center or coordinates, not both. If neither provided, uses placeId coordinates as center.
Example with Coordinates Array
curl -X POST https://gtrack-api.wiremo.co/api/initiate-scan \
  -H "Content-Type: application/json" \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -d '{
    "coordinates": [
      [40.7128, -74.0060],
      [40.7138, -74.0070],
      [40.7148, -74.0080],
      [40.7158, -74.0090],
      [40.7168, -74.0100],
      [40.7178, -74.0110],
      [40.7188, -74.0120],
      [40.7198, -74.0130],
      [40.7208, -74.0140]
    ],
    "gridSize": 3,
    "scanType": "maps",
    "keyword": "pizza restaurant",
    "placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "customZoom": 18,
    "language": "es"
  }'
curl -X POST https://gtrack-api.wiremo.co/api/initiate-scan \
  -H "Content-Type: application/json" \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -d '{
    "center": [40.7128, -74.0060],
    "gridSize": 5,
    "distance": 0.5,
    "unit": "kilometer",
    "scanType": "maps",
    "keyword": "coffee shop",
    "placeId": "ChIJOwg_06VPwokRYv534QaPC8g",
    "customZoom": 18,
    "language": "es"
  }'
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/initiate-scan', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'gtrk_demo_sample_key_here'
  },
  body: JSON.stringify({
    center: [40.7128, -74.0060],
    gridSize: 5,
    distance: 0.5,
    unit: 'kilometer',
    scanType: 'maps',
    keyword: 'coffee shop',
    placeId: 'ChIJOwg_06VPwokRYv534QaPC8g',
    customZoom: 18,
    language: 'es'
  })
});
console.log(await res.json());
import requests

url = 'https://gtrack-api.wiremo.co/api/initiate-scan'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
payload = {
  'center': [40.7128, -74.0060],
  'gridSize': 5,
  'distance': 0.5,
  'unit': 'kilometer',
  'scanType': 'maps',
  'keyword': 'coffee shop',
  'placeId': 'ChIJOwg_06VPwokRYv534QaPC8g',
  'customZoom': 18,
  'language': 'es'
}
resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
 [40.7128, -74.0060],
  'gridSize' => 5,
  'distance' => 0.5,
  'unit' => 'kilometer',
  'scanType' => 'maps',
  'keyword' => 'coffee shop',
  'placeId' => 'ChIJOwg_06VPwokRYv534QaPC8g',
  'customZoom' => 18,
  'language' => 'es'
];
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Content-Type: application/json',
    'x-api-key: gtrk_demo_sample_key_here'
  ],
  CURLOPT_POSTFIELDS => json_encode($payload)
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import { useState } from 'react';

export default function StartScan(){
  const [result,setResult] = useState(null);
  const start = async () => {
    const res = await fetch('https://gtrack-api.wiremo.co/api/initiate-scan', {
      method: 'POST',
      headers: { 'Content-Type':'application/json', 'x-api-key':'gtrk_demo_sample_key_here' },
      body: JSON.stringify({
        center: [40.7128, -74.0060], 
        gridSize: 5, 
        distance: 0.5, 
        unit: 'kilometer',
        scanType: 'maps', 
        keyword: 'coffee shop', 
        placeId: 'ChIJOwg_06VPwokRYv534QaPC8g',
        customZoom: 18,
        language: 'es'
      })
    });
    setResult(await res.json());
  };
  return (
    
{JSON.stringify(result,null,2)}
); }
200 OK example shown earlier in original docs.
Errors: 403 missing scans:write, 400 insufficient credits / invalid params, 404 user not found, 500 scraper unavailable.

POST Initiate Scan – With Polling

/api/initiate-scan?wait=true&timeout=300
Polling: initial delay 5s · interval 30s · max 600s. Returns full results when status = COMPLETED.
curl -X POST "https://gtrack-api.wiremo.co/api/initiate-scan?wait=true&timeout=300" \
  -H "Content-Type: application/json" \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -d '{
    "center": [51.5074, -0.1278],
    "gridSize": 4,
    "distance": 0.3,
    "unit": "kilometer",
    "scanType": "maps",
    "keyword": "hotel",
    "placeId": "ChIJ3S-JXmauEmsRUcIaWtf4MzE",
    "customZoom": 18,
    "language": "es"
  }'
import fetch from 'node-fetch';

const url = 'https://gtrack-api.wiremo.co/api/initiate-scan?wait=true&timeout=300';
const payload = {
  center: [51.5074, -0.1278],
  gridSize: 3,
  distance: 300,
  unit: 'kilometer',
  scanType: 'both',
  keyword: 'hotel',
  placeId: 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
  businessName: 'The London Hotel'
};

const res = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'gtrk_live_0202d7fcf814dd6f09d0feee'
  },
  body: JSON.stringify(payload)
});
console.log(await res.json());
 [51.5074, -0.1278],
  'gridSize' => 4,
  'distance' => 0.3,
  'unit' => 'kilometer',
  'scanType' => 'maps',
  'keyword' => 'hotel',
  'placeId' => 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
  'customZoom' => 18,
  'language' => 'es'
];

curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Content-Type: application/json',
    'x-api-key: gtrk_demo_sample_key_here'
  ],
  CURLOPT_POSTFIELDS => json_encode($payload)
]);

$response = curl_exec($ch);

if ($response === false) {
  $error = curl_error($ch);
  curl_close($ch);
  http_response_code(500);
  echo json_encode(['error' => $error], JSON_PRETTY_PRINT);
  exit;
}

curl_close($ch);
header('Content-Type: application/json');
echo $response;
?>
import { useState } from 'react';

export default function StartScan() {
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);
  const start = async () => {
    setError(null);
    setResult(null);
    try {
      const res = await fetch('https://gtrack-api.wiremo.co/api/initiate-scan?wait=true&timeout=300', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': 'gtrk_demo_sample_key_here'
        },
        body: JSON.stringify({
          center: [51.5074, -0.1278],
          gridSize: 4,
          distance: 0.3,
          unit: 'kilometer',
          scanType: 'maps',
          keyword: 'hotel',
          placeId: 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
          customZoom: 18,
          language: 'es'
        })
      });

      const data = await res.json().catch(() => null);
      if (!res.ok) {
        throw new Error(data?.message || 'Request failed');
      }
      setResult(data);
    } catch (e) {
      setError(e.message);
    }
  };

  return (
    
{error &&
{error}
} {result &&
{JSON.stringify(result, null, 2)}
}
); }
import requests

url = 'https://gtrack-api.wiremo.co/api/initiate-scan'
params = {'wait': 'true', 'timeout': 300}
headers = {'x-api-key': 'gtrk_live_0202d7fcf814dd6f09d0feee', 'Content-Type': 'application/json'}
payload = {
  'center': [51.5074, -0.1278],
  'gridSize': 3,
  'distance': 300,
  'unit': 'kilometer',
  'scanType': 'both',
  'keyword': 'hotel',
  'placeId': 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
  'businessName': 'The London Hotel'
}
resp = requests.post(url, params=params, json=payload, headers=headers)
print(resp.json())
 [51.5074, -0.1278],
  'gridSize' => 4,
  'distance' => 0.3,
  'unit' => 'kilometer',
  'scanType' => 'maps',
  'keyword' => 'hotel',
  'placeId' => 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
  'customZoom' => 18,
  'language' => 'es'
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
  'x-api-key: gtrk_demo_sample_key_here'
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import { useState } from 'react';

function ScanWithPolling() {
  const [results, setResults] = useState(null);
  const [loading, setLoading] = useState(false);

  const startScan = async () => {
    setLoading(true);
    const response = await fetch(
      'https://gtrack-api.wiremo.co/api/initiate-scan?wait=true&timeout=300',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': 'gtrk_demo_sample_key_here'
        },
        body: JSON.stringify({
          center: [51.5074, -0.1278],
          gridSize: 4,
          distance: 0.3,
          unit: 'kilometer',
          scanType: 'maps',
          keyword: 'hotel',
          placeId: 'ChIJ3S-JXmauEmsRUcIaWtf4MzE',
          customZoom: 18,
          language: 'es'
        })
      }
    );
    const data = await response.json();
    setResults(data);
    setLoading(false);
  };

  return (
    
{results &&
{JSON.stringify(results, null, 2)}
}
); }
Returns COMPLETED payload or a timeout envelope advising to poll by taskId.

POST Generate Grid Coordinates

/api/generate-grid

Generates grid coordinates without initiating a scan.

curl -X POST https://gtrack-api.wiremo.co/api/generate-grid \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "center": [28.0833107, -16.7223283],
    "gridSize": 3,
    "distance": 8,
    "unit": "kilometer"
  }'
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/generate-grid', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'gtrk_demo_sample_key_here'
  },
  body: JSON.stringify({
    center: [28.0833107, -16.7223283],
    gridSize: 3,
    distance: 8,
    unit: 'kilometer'
  })
});
console.log(await res.json());
import requests

url = 'https://gtrack-api.wiremo.co/api/generate-grid'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
payload = {
  'center': [28.0833107, -16.7223283],
  'gridSize': 3,
  'distance': 8,
  'unit': 'kilometer'
}
resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
 [40.7128, -74.0060],
  'gridSize' => 5,
  'distance' => 1.0,
  'unit' => 'kilometer'
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
  'x-api-key: gtrk_demo_sample_key_here'
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import { useState } from 'react';

function GenerateGrid() {
  const [grid, setGrid] = useState([]);

  const generateGrid = async () => {
    const response = await fetch('https://gtrack-api.wiremo.co/api/generate-grid', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'gtrk_demo_sample_key_here'
      },
      body: JSON.stringify({
        center: [40.7128, -74.0060],
        gridSize: 5,
        distance: 1.0,
        unit: 'kilometer'
      })
    });
    const data = await response.json();
    setGrid(data.coordinates);
  };

  return (
    
{grid.length > 0 &&

Generated {grid.length} coordinates

}
); }

GET Get Scan Status / Results

/api/fullstatus/scan/:taskId/status

Get the current status and results of a scan by task ID.

Errors: 404 not found/ownership, 403 forbidden.
curl -X GET https://gtrack-api.wiremo.co/api/fullstatus/scan/TASK_ID_HERE/status \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const taskId = 'TASK_ID_HERE';
const res = await fetch(`https://gtrack-api.wiremo.co/api/fullstatus/scan/${taskId}/status`, {
  method: 'GET',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

task_id = 'TASK_ID_HERE'
url = f'https://gtrack-api.wiremo.co/api/fullstatus/scan/{task_id}/status'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.get(url, headers=headers)
print(resp.json())
<?php
$taskId = 'TASK_ID_HERE';
$url = "https://gtrack-api.wiremo.co/api/fullstatus/scan/{$taskId}/status";
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React, { useState, useEffect } from 'react';

function ScanStatus({ taskId }) {
  const [status, setStatus] = useState(null);

  useEffect(() => {
    const fetchStatus = async () => {
      const response = await fetch(`https://gtrack-api.wiremo.co/api/fullstatus/scan/${taskId}/status`, {
        headers: {
          'x-api-key': 'gtrk_demo_sample_key_here',
          'Content-Type': 'application/json'
        }
      });
      const data = await response.json();
      setStatus(data);
    };
    if (taskId) fetchStatus();
  }, [taskId]);

  return (
    
{status &&
{JSON.stringify(status, null, 2)}
}
); }

GET List All Scans

/api/scans

Retrieve all scans for the authenticated user.

curl -X GET https://gtrack-api.wiremo.co/api/scans \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/scans', {
  method: 'GET',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

url = 'https://gtrack-api.wiremo.co/api/scans'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.get(url, headers=headers)
print(resp.json())
<?php
$url = 'https://gtrack-api.wiremo.co/api/scans';
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React, { useState, useEffect } from 'react';

function ScanList() {
  const [scans, setScans] = useState([]);

  useEffect(() => {
    const fetchScans = async () => {
      const response = await fetch('https://gtrack-api.wiremo.co/api/scans', {
        headers: {
          'x-api-key': 'gtrk_demo_sample_key_here',
          'Content-Type': 'application/json'
        }
      });
      const data = await response.json();
      setScans(data);
    };
    fetchScans();
  }, []);

  return (
    
{scans.map(scan => (
{scan.taskId}
))}
); }

GET List Scheduled Scans

/api/scheduled-scans

Retrieve your scheduled scan configurations. Returns a JSON array of schedule objects — each includes _id (use it with Run History below), name, frequency, timeOfDay, timezone, isActive, lastRunTime, nextRunTime and scanParameters (business name, place ID, keywords, grid size, distance).

Note: completed runs from scheduled scans are intentionally not included in GET /api/scans (that endpoint lists one-off scans). Use Run History below to retrieve scheduled results.

curl -X GET https://gtrack-api.wiremo.co/api/scheduled-scans \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/scheduled-scans', {
  headers: { 'x-api-key': 'gtrk_demo_sample_key_here' }
});
const schedules = await res.json();
console.log(schedules.map(s => ({ id: s._id, name: s.name, next: s.nextRunTime })));
import requests

url = 'https://gtrack-api.wiremo.co/api/scheduled-scans'
headers = {'x-api-key': 'gtrk_demo_sample_key_here'}
schedules = requests.get(url, headers=headers).json()
for s in schedules:
    print(s['_id'], s['name'], s.get('nextRunTime'))

GET Scheduled Scan Run History

/api/scheduled-scans/:id/history

Retrieve run results for one schedule (:id from List Scheduled Scans), most recent first. Each entry includes keyword, businessName, scanDate, status (pending | completed | failed), averageRanking and — for completed runs — the taskId.

Pagination (recommended): pass ?page= and ?limit= (default 50, max 200) to receive { success, pagination: { page, limit, total, pages }, results: [...] }. Without these parameters the endpoint returns the complete history as a plain JSON array — fine for small schedules, heavy for long-running daily ones.

Feed each taskId into GET /api/fullstatus/scan/:taskId/status to obtain the full grid data for that run.

Share links for a schedule can also be managed with your API key: POST /api/scheduled-scans/:id/share creates one, DELETE removes it.

curl -X GET "https://gtrack-api.wiremo.co/api/scheduled-scans/SCHEDULE_ID_HERE/history?page=1&limit=50" \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const scheduleId = 'SCHEDULE_ID_HERE';
const res = await fetch(`https://gtrack-api.wiremo.co/api/scheduled-scans/${scheduleId}/history?page=1&limit=50`, {
  headers: { 'x-api-key': 'gtrk_demo_sample_key_here' }
});
const { pagination, results } = await res.json();
const completed = results.filter(r => r.status === 'completed');
console.log(completed.map(r => ({ taskId: r.taskId, keyword: r.keyword, date: r.scanDate })));
import requests

schedule_id = 'SCHEDULE_ID_HERE'
url = f'https://gtrack-api.wiremo.co/api/scheduled-scans/{schedule_id}/history'
headers = {'x-api-key': 'gtrk_demo_sample_key_here'}
data = requests.get(url, headers=headers, params={'page': 1, 'limit': 50}).json()
for r in data['results']:
    if r['status'] == 'completed':
        print(r['taskId'], r['keyword'], r['scanDate'])

GET Get All Tracked Businesses

/api/tracked-businesses

Retrieve all tracked businesses for the authenticated user.

curl -X GET https://gtrack-api.wiremo.co/api/tracked-businesses \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/tracked-businesses', {
  method: 'GET',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

url = 'https://gtrack-api.wiremo.co/api/tracked-businesses'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.get(url, headers=headers)
print(resp.json())
<?php
$url = 'https://gtrack-api.wiremo.co/api/tracked-businesses';
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React, { useState, useEffect } from 'react';

function TrackedBusinessesList() {
  const [businesses, setBusinesses] = useState([]);

  useEffect(() => {
    const fetchBusinesses = async () => {
      const response = await fetch('https://gtrack-api.wiremo.co/api/tracked-businesses', {
        headers: {
          'x-api-key': 'gtrk_demo_sample_key_here',
          'Content-Type': 'application/json'
        }
      });
      const data = await response.json();
      setBusinesses(data);
    };
    fetchBusinesses();
  }, []);

  return (
    
{businesses.map(business => (
{business.businessName}
))}
); }

POST Add Tracked Business

/api/tracked-businesses

businesses:write

curl -X POST https://gtrack-api.wiremo.co/api/tracked-businesses \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "placeId": "ChIJ9X1uQniZagwR8nA2NqxFEb8",
    "businessName": "Dulce Vegan",
    "trackingParams": {"rating": true, "reviewCount": true, "position": false, "hours": false},
    "alertThresholds": {"ratingChange": 0.5, "reviewCountChange": 10},
    "alertFrequency": "daily",
    "alertEmail": "user@example.com"
  }'
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/api/tracked-businesses', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'gtrk_live_0202d7fcf814dd6f09d0feee'
  },
  body: JSON.stringify({
    placeId: 'ChIJ9X1uQniZagwR8nA2NqxFEb8',
    businessName: 'Dulce Vegan',
    trackingParams: { rating: true, reviewCount: true, position: false, hours: false },
    alertThresholds: { ratingChange: 0.5, reviewCountChange: 10 },
    alertFrequency: 'daily',
    alertEmail: 'user@example.com'
  })
});
console.log(await res.json());
<?php
$ch = curl_init('https://gtrack-api.wiremo.co/api/tracked-businesses');

$payload = [
  'placeId' => 'ChIJ9X1uQniZagwR8nA2NqxFEb8',
  'businessName' => 'Dulce Vegan',
  'trackingParams' => [
    'rating' => true,
    'reviewCount' => true,
    'position' => false,
    'hours' => false
  ],
  'alertThresholds' => [
    'ratingChange' => 0.5,
    'reviewCountChange' => 10
  ],
  'alertFrequency' => 'daily',
  'alertEmail' => 'user@example.com'
];

curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Content-Type: application/json',
    'x-api-key: gtrk_live_0202d7fcf814dd6f09d0feee'
  ],
  CURLOPT_POSTFIELDS => json_encode($payload)
]);

$response = curl_exec($ch);
if ($response === false) {
  http_response_code(500);
  echo 'cURL error: ' . curl_error($ch);
} else {
  header('Content-Type: application/json');
  echo $response;
}

curl_close($ch);
?>
import requests

url = 'https://gtrack-api.wiremo.co/api/tracked-businesses'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
payload = {
  'placeId': 'ChIJ9X1uQniZagwR8nA2NqxFEb8',
  'businessName': 'Dulce Vegan',
  'trackingParams': {'rating': True, 'reviewCount': True, 'position': False, 'hours': False},
  'alertThresholds': {'ratingChange': 0.5, 'reviewCountChange': 10},
  'alertFrequency': 'daily',
  'alertEmail': 'user@example.com'
}
resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
import React, { useState } from 'react';

function AddTrackedBusiness() {
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);

  const addBusiness = async () => {
    setLoading(true);
    const response = await fetch('https://gtrack-api.wiremo.co/api/tracked-businesses', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'gtrk_demo_sample_key_here'
      },
      body: JSON.stringify({
        placeId: 'ChIJ9X1uQniZagwR8nA2NqxFEb8',
        businessName: 'Dulce Vegan',
        trackingParams: { rating: true, reviewCount: true, position: false, hours: false },
        alertThresholds: { ratingChange: 0.5, reviewCountChange: 10 },
        alertFrequency: 'daily',
        alertEmail: 'user@example.com'
      })
    });
    const data = await response.json();
    setResult(data);
    setLoading(false);
  };

  return (
    
{result &&
{JSON.stringify(result, null, 2)}
}
); }
Errors: plan limit reached, too many tracking params for plan.

GET List AI Reports

/reports-ai/reports/list

Retrieve all saved AI scan reports for the authenticated user.

curl -X GET https://gtrack-api.wiremo.co/reports-ai/reports/list \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const res = await fetch('https://gtrack-api.wiremo.co/reports-ai/reports/list', {
  method: 'GET',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

url = 'https://gtrack-api.wiremo.co/reports-ai/reports/list'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.get(url, headers=headers)
print(resp.json())
<?php
$url = 'https://gtrack-api.wiremo.co/reports-ai/reports/list';
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React, { useState, useEffect } from 'react';

function AIReportsList() {
  const [reports, setReports] = useState([]);

  useEffect(() => {
    const fetchReports = async () => {
      const response = await fetch('https://gtrack-api.wiremo.co/reports-ai/reports/list', {
        headers: {
          'x-api-key': 'gtrk_demo_sample_key_here',
          'Content-Type': 'application/json'
        }
      });
      const data = await response.json();
      setReports(data);
    };
    fetchReports();
  }, []);

  return (
    
{reports.map(report => (
{report.title}
))}
); }

GET Get AI Report Details

/reports-ai/reports/:reportId

Get detailed data for a specific AI scan report.

curl -X GET https://gtrack-api.wiremo.co/reports-ai/reports/REPORT_ID_HERE \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const reportId = 'REPORT_ID_HERE';
const res = await fetch(`https://gtrack-api.wiremo.co/reports-ai/reports/${reportId}`, {
  method: 'GET',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

report_id = 'REPORT_ID_HERE'
url = f'https://gtrack-api.wiremo.co/reports-ai/reports/{report_id}'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.get(url, headers=headers)
print(resp.json())
<?php
$reportId = 'REPORT_ID_HERE';
$url = "https://gtrack-api.wiremo.co/reports-ai/reports/{$reportId}";
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React, { useState, useEffect } from 'react';

function AIReportDetails({ reportId }) {
  const [report, setReport] = useState(null);

  useEffect(() => {
    const fetchReport = async () => {
      const response = await fetch(`https://gtrack-api.wiremo.co/reports-ai/reports/${reportId}`, {
        headers: {
          'x-api-key': 'gtrk_demo_sample_key_here',
          'Content-Type': 'application/json'
        }
      });
      const data = await response.json();
      setReport(data);
    };
    fetchReport();
  }, [reportId]);

  return (
    
{report &&
{report.title}
}
); }

DELETE Delete AI Report

/reports-ai/reports/:reportId

Delete a specific AI scan report permanently.

curl -X DELETE https://gtrack-api.wiremo.co/reports-ai/reports/REPORT_ID_HERE \
  -H "x-api-key: gtrk_demo_sample_key_here" \
  -H "Content-Type: application/json"
import fetch from 'node-fetch';

const reportId = 'REPORT_ID_HERE';
const res = await fetch(`https://gtrack-api.wiremo.co/reports-ai/reports/${reportId}`, {
  method: 'DELETE',
  headers: {
    'x-api-key': 'gtrk_demo_sample_key_here',
    'Content-Type': 'application/json'
  }
});
console.log(await res.json());
import requests

report_id = 'REPORT_ID_HERE'
url = f'https://gtrack-api.wiremo.co/reports-ai/reports/{report_id}'
headers = {'x-api-key': 'gtrk_demo_sample_key_here', 'Content-Type': 'application/json'}
resp = requests.delete(url, headers=headers)
print(resp.json())
<?php
$reportId = 'REPORT_ID_HERE';
$url = "https://gtrack-api.wiremo.co/reports-ai/reports/{$reportId}";
$headers = [
  'x-api-key: gtrk_demo_sample_key_here',
  'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
import React from 'react';

function DeleteAIReport({ reportId, onDelete }) {
  const handleDelete = async () => {
    const response = await fetch(`https://gtrack-api.wiremo.co/reports-ai/reports/${reportId}`, {
      method: 'DELETE',
      headers: {
        'x-api-key': 'gtrk_demo_sample_key_here',
        'Content-Type': 'application/json'
      }
    });
    const result = await response.json();
    onDelete(result);
  };

  return (
    
  );
}

AI Tracker — overview

AI Tracker measures whether AI engines (ChatGPT, Perplexity, Gemini, Google AI Mode) mention and recommend a business when customers ask. Each campaign holds prompts (grouped by topic); a scan runs every prompt against every engine, producing a prompt × engine matrix of mention / position / sentiment / cited sources. All endpoints below are under the standard base URL and accept your x-api-key.

Read endpoints are free. Trigger Scan consumes the campaign's scan allowance (paid extras are purchased in the dashboard). Generate endpoints consume your account AI-token allowance (the same pool as AI Reports) when called via the API/MCP — see below. Campaigns can be created via the API when a slot is available (the free taste slot, or an already-paid subscription slot); if a new paid slot would be required, create returns 402 with { reason, priceUsd } — buy the subscription in the dashboard, then retry. Editing config, archiving/deleting a campaign, and managing the subscription remain dashboard-only (owner) and are not part of this API.

GET List Campaigns

/api/ai-tracker/campaigns

Returns { success, campaigns: [...] }. Each campaign includes _id, businessName, domain, engines, schedule, promptCount, lastAiScore (0–100, share of checks that mentioned the business), lastRunAt, and activeScan (true while a scan is running).

curl -X GET https://gtrack-api.wiremo.co/api/ai-tracker/campaigns \
  -H "x-api-key: gtrk_demo_sample_key_here"

GET Get Campaign

/api/ai-tracker/campaigns/:id

Returns { success, campaign, manualRunUsage }. manualRunUsage reports the free-scan allowance for the current billing period: { used, included, remaining, resetsAt, isLifetime }.

curl https://gtrack-api.wiremo.co/api/ai-tracker/campaigns/CAMPAIGN_ID \
  -H "x-api-key: gtrk_demo_sample_key_here"

Prompts for a campaign: GET /api/ai-tracker/campaigns/:id/prompts.

POST Create Campaign

/api/ai-tracker/campaigns

Body: { businessName, domain, businessDescription?, brandAliases?, location?: { country }, language?, engines?: ["chatgpt","perplexity","gemini","google_ai_mode"], schedule?: { frequency, dayOfWeek?, dayOfMonth?, hour?, minute?, timezone?, enabled? }, notifications?: { emailOnComplete, email } }. Returns { success, campaign }.

Slots: creation succeeds when a slot is free — the included taste campaign (monthly schedule only) or an already-paid subscription slot. If a new paid slot would be needed, returns 402 { reason:"subscription_required", priceUsd }; purchase the subscription in the dashboard, then retry.

Typical automation flow: generate/topics → generate/prompts (both metered) → POST /campaigns → POST /campaigns/:id/prompts → POST /campaigns/:id/run.

curl -X POST https://gtrack-api.wiremo.co/api/ai-tracker/campaigns \
  -H "x-api-key: gtrk_demo_sample_key_here" -H "Content-Type: application/json" \
  -d '{"businessName":"Acme Bakery","domain":"acme.example","engines":["chatgpt","perplexity","gemini","google_ai_mode"],"schedule":{"frequency":"weekly","dayOfWeek":1,"hour":9,"timezone":"UTC","enabled":true}}'

Add prompts after creating: POST /api/ai-tracker/campaigns/:id/prompts with { prompts: [{ text, topic, intent }] }.

GET List Runs & Get Results

/api/ai-tracker/campaigns/:id/runs

Scan history for a campaign, newest first. Each run: runId, status (pending|running|completed|partial|failed|cancelled), engines, expectedCells, completedCells, mentionedCells, startedAt, completedAt.

/api/ai-tracker/runs/:runId/results

The result matrix for one run: { success, run, results } where each result is one prompt × engine cell — engine, mentioned, mentionType, position, sentiment, brands (ordered, with domain and isTarget), sources. Heavy answer text is omitted here; fetch a single cell with GET /api/ai-tracker/results/:id (add ?raw=1 for the raw engine payload).

/api/ai-tracker/runs/:runId/export

Full-detail CSV of a run (prompt, topic, intent, per-engine mention/position/sentiment, ranked brands with domains, source URLs, answer excerpt).

RUN=$(curl -s https://gtrack-api.wiremo.co/api/ai-tracker/campaigns/CAMPAIGN_ID/runs \
  -H "x-api-key: gtrk_demo_sample_key_here" | jq -r '.runs[0].runId')
curl "https://gtrack-api.wiremo.co/api/ai-tracker/runs/$RUN/results" \
  -H "x-api-key: gtrk_demo_sample_key_here"

GET Progression

/api/ai-tracker/campaigns/:id/progression

Run-over-run trend (last 12 finished runs): per run the aiScore, per-engine hit rates, and per-prompt mention counts + average position. Ideal for charting AI-visibility change over time.

POST Trigger Scan

/api/ai-tracker/campaigns/:id/run

Queues a scan (executed by the AI Tracker worker; poll /runs for status). Subject to the campaign's scan allowance — if the included scans are used, returns 402 with { reason, priceUsd }; extra scans are purchased in the dashboard.

curl -X POST https://gtrack-api.wiremo.co/api/ai-tracker/campaigns/CAMPAIGN_ID/run \
  -H "x-api-key: gtrk_demo_sample_key_here"

POST Generate Topics / Prompts metered

/api/ai-tracker/generate/topics  ·  /api/ai-tracker/generate/prompts

These consume your account AI-token allowance (the same pool as AI Reports: plan token limit + ai_tokens extra credits) when called with an API key or via MCP. If the balance is too low the call returns 403 with { reason: "insufficient_ai_tokens", tokensAvailable }. Topic/prompt generation in the dashboard is free; metering applies only to programmatic use.

generate/topics body: { businessName, domain, businessDescription?, country?, language? } → { topics: [{ topic, intent }] }. generate/prompts body: { businessName, domain?, businessDescription?, country?, language?, topics: [{ topic, intent }] } → { prompts: [{ topic, intent, text }] }.

curl -X POST https://gtrack-api.wiremo.co/api/ai-tracker/generate/topics \
  -H "x-api-key: gtrk_demo_sample_key_here" -H "Content-Type: application/json" \
  -d '{"businessName":"Acme Bakery","domain":"acme.example","country":"United States","language":"en"}'

Credit Calculation

Scan TypeMultiplierFormula
Maps1xcoordinates × keywords × 1
Local2xcoordinates × keywords × 2
Examples: 9×1×maps=9 · 9×1×local=18

Common Error Codes

CodeDescriptionCommon Causes
400Bad RequestMissing/invalid params, insufficient credits
401UnauthorizedMissing API key, invalid format
403ForbiddenMissing scope, feature not in plan, plan limit reached
404Not FoundResource absent, wrong owner, scan not completed
500Internal Server ErrorScraper/DB/AI service error
429Too Many RequestsRate limits exceeded; see Retry-After

Retry-After Header

When you receive a 429 (Too Many Requests) response, the server includes a Retry-After header indicating when you can retry your request.

HeaderValueDescription
Retry-AfterSeconds (integer)Number of seconds to wait before retrying
Retry-AfterHTTP DateSpecific date/time when you can retry
Example Usage
Retry-After: 60 - Wait 60 seconds before retrying
Retry-After: Wed, 21 Oct 2015 07:28:00 GMT - Retry after this specific time

Best Practices

Security
  • Never expose API keys in client-side code
  • Rotate keys; use separate keys per env
  • Prefer IP restrictions
Performance
  • Use polling only when immediate results are needed
  • Cache results; paginate large lists
  • Generate grid once; reuse
Cost Optimization
  • Prefer maps (1x) unless local pack is required
  • Start with smaller grids; schedule scans
  • Check credits before large runs
Error Handling
  • Handle 400/403 for credits/scopes
  • Retry 5xx with backoff
  • Poll status if not using wait=true

Changelog

DateVersionChanges
2025-10-22v1.0Initial release: scan management, competitor tracking, AI reports
2026-08-31v1.1Scheduled Scans: list schedules (GET /api/scheduled-scans) and per-schedule run history with task IDs (GET /api/scheduled-scans/:id/history); share-link create/delete accept API keys; history supports page/limit; MCP gains get_scheduled_scans and get_scheduled_scan_history tools
2026-09-08v1.2AI Tracker: campaigns, runs & result matrix, progression, CSV export, trigger scan, and metered topic/prompt generation via API + MCP (/api/ai-tracker/*); generation consumes the account AI-token pool on programmatic calls. MCP gains ai_tracker_list_campaigns, ai_tracker_get_campaign, ai_tracker_list_runs, ai_tracker_get_results, ai_tracker_get_progression, ai_tracker_run_scan, ai_tracker_suggest_topics, ai_tracker_generate_prompts
Need help?
Documentation 1.0 Last updated: 15 Sep 2026