Skip to content

Developers

The gx360 API and webhooks.

Pull your growth data into your own dashboards and tools, and get told the moment work moves, a lead arrives or an audit finishes. Read-only REST, signed webhooks, plain JSON.

Getting started

Three steps to your first call.

  1. The API and webhooks are part of the Scale plan and Done for you.
  2. A company admin opens Settings → Developers in gx360 and creates an API key. The key is shown once: store it like a password.
  3. Call the API with the key. Every call is read-only, and a key only ever sees its own company’s data.
Your first request
curl https://app.gx360.ai/api/public/v1/work-items?status=open&per_page=20 \
  -H "Authorization: Bearer gx_live_xxxxxxxxxx_…"

Authentication

One bearer key per integration.

Send the key in the Authorization header: Bearer gx_live_…. Keys never go in the URL. Make a separate key for each tool, named after it, so you can revoke one without breaking the others. Settings shows when each key was last used.

Base URL: https://app.gx360.ai/api/public/v1

Requests and pages

JSON in a data envelope.

Lists are paged: pass page and per_page (up to 100, default 50). The meta object says whether there is more. Dates are ISO 8601 in UTC; filters like updated_since take a date or a date-time. To sync, keep the newest updated_at you saw and pass it next time.

200 OK — GET /work-items
{
  "data": [
    {
      "id": 4182,
      "site_id": 3,
      "title": "Pricing page title is cut off in Google",
      "category": "seo",
      "priority": "P1",
      "status": "open",
      "page": "/pricing",
      "keyword": "time tracking software pricing",
      "summary": "The title is 74 characters…",
      "expected_impact": 0.6,
      "created_at": "2026-10-07T06:12:40+00:00",
      "updated_at": "2026-10-07T06:12:40+00:00"
    }
  ],
  "meta": { "page": 1, "per_page": 20, "total": 37, "has_more": true }
}

Errors and limits

Plain status codes, one error shape.

Each key may make 120 requests a minute. Errors always look like this:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests: 120 a minute per key."
  }
}
StatuscodeMeaning
401unauthenticatedThe key is missing, wrong or revoked.
403plan_requiredThe company’s plan does not include the API.
404not_foundNo such record in your company (another company’s site_id is also 404).
422invalid_requestA parameter is wrong; fields says which.
429rate_limitedMore than 120 requests a minute on this key. Wait for Retry-After seconds.

Endpoints

What you can read.

EndpointReturnsParameters
GET /sitesYour websites.—
GET /work-itemsProblems and proposals on the Work board, newest change first.site_id, status, updated_since, page, per_page
GET /kra-plansKRA plans with each KRA, its KPIs, targets and current values.site_id
GET /leadsLeads from your connected CRM.site_id, status, created_since, page, per_page
GET /search/summaryGoogle Search clicks, impressions, CTR and position against the previous period.site_id (required), days = 7, 28 or 90
GET /audit/summaryThe latest site audit and open problems by severity.site_id (required)

Work item status is one of open, accepted, in_progress, done, dismissed or superseded. Need something that isn’t here? Tell us; the API grows with what customers use.

Webhooks

Get told, instead of asking.

In Settings → Developers, add an https endpoint and pick its events. You get a signing secret once; use it to check each delivery. Send test posts a ping event so you can check your receiver straight away.

EventSent when
work_item.createdA new problem or proposal is on the Work board.
work_item.status_changedA work item moved: accepted, in progress, done, dismissed… Includes previous_status.
kra_plan.scoredA KRA plan was scored again, with its new overall score.
lead.createdA new lead arrived from your CRM.
audit.completedA site audit finished.

The data object uses the same fields as the API. The body’s id equals the X-Gx360-Delivery header and stays the same across retries, so you can ignore one you’ve already handled.

A delivery
POST /hooks/gx360 HTTP/1.1
Content-Type: application/json
User-Agent: gx360-webhooks/1
X-Gx360-Event: work_item.status_changed
X-Gx360-Delivery: 9b0d6f3e-2c8a-4f8e-9a51-3c1f7d2b6e10
X-Gx360-Signature: t=1791360000,v1=5f2b…c9

{
  "id": "9b0d6f3e-2c8a-4f8e-9a51-3c1f7d2b6e10",
  "event": "work_item.status_changed",
  "created_at": "2026-10-07T09:20:00+00:00",
  "data": {
    "id": 4182,
    "site_id": 3,
    "title": "Pricing page title is cut off in Google",
    "status": "done",
    "previous_status": "in_progress"
  }
}

Verifying signatures

Check every delivery came from gx360.

X-Gx360-Signature holds a Unix time t and v1, the hex HMAC-SHA256 of "<t>.<raw body>" with your endpoint’s secret. Compute it over the raw body before parsing the JSON, compare in constant time, and reject anything older than five minutes.

Node.js
import crypto from 'node:crypto'

// rawBody: the request body exactly as received (a Buffer or string).
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  if (age > 300) return false // older than five minutes: reject

  const expected = crypto.createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`).digest('hex')
  const given = Buffer.from(parts.v1 ?? '')
  return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), given)
}
PHP
<?php
function gx360_verify(string $rawBody, string $header, string $secret): bool
{
    parse_str(str_replace(',', '&', $header), $parts);
    if (abs(time() - (int) ($parts['t'] ?? 0)) > 300) {
        return false; // older than five minutes: reject
    }
    $expected = hash_hmac('sha256', $parts['t'].'.'.$rawBody, $secret);

    return hash_equals($expected, $parts['v1'] ?? '');
}

Retries

Answer 2xx within ten seconds.

Any other answer, a timeout or a redirect counts as a failure. We retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then mark the delivery failed. The delivery log in Settings shows each attempt and what your server answered. After 50 failures in a row the endpoint is paused; switch it back on when it’s fixed. Do slow work after you’ve answered, not before.