> ## Documentation Index
> Fetch the complete documentation index at: https://docs.growthmention.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Any other site (HTTP API)

> Report AI crawler visits and AI referrals to Growth Mention from any server, CDN or edge worker over HTTP.

Not on WordPress? Anything that can make an HTTPS request can report to Agent Analytics — your web server, a CDN or edge worker, a log shipper, or a scheduled job reading your access logs.

You send batches of requests your site served. Growth Mention keeps the ones from recognised AI crawlers, checks them, and discards the rest.

## Before you start

Create an **ingest key** in your project under **Agent analytics → Integration**. It's shown only once, so copy it straight away. Only organization admins can create keys. Use a separate key for each site.

## The request

```bash theme={null}
curl https://app.growthmention.com/api/v1/ingest/agent-analytics \
  -H "Authorization: Bearer YOUR_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "ts": "2026-09-21T08:14:03Z",
        "ua": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot",
        "path": "/pricing",
        "status": 200,
        "ip": "20.171.207.45"
      }
    ],
    "totals": [
      { "day": "2026-09-21", "hits": 18342 }
    ],
    "referrals": [
      { "ts": "2026-09-21T08:20:11Z", "source": "chatgpt", "path": "/pricing" }
    ]
  }'
```

All three arrays are optional — send whichever you have.

## Crawler visits: `events`

One entry per request your site served. You can send **all** your traffic: anything that isn't a recognised AI crawler is dropped on arrival and never stored.

<ParamField body="status" type="integer" required>
  The HTTP status code your site returned, 100–599. This is what shows a crawler being blocked (403) or sent to a missing page (404).
</ParamField>

<ParamField body="ua" type="string" required>
  The request's user agent. It's how the crawler is recognised. Up to 512 characters are kept.
</ParamField>

<ParamField body="ip" type="string">
  The address the request came from. Needed to verify the crawler is genuine — without it, a visit can't be verified and isn't counted. Deleted after 30 days.
</ParamField>

<ParamField body="path" type="string">
  The path requested, e.g. `/pricing`. The query string is removed before storing. Defaults to `/`.
</ParamField>

<ParamField body="ts" type="string">
  When the request happened, in ISO 8601. Defaults to the time of your request.
</ParamField>

<Warning>
  Send the **real client IP**, not your load balancer's or CDN's. Behind a proxy that's usually in a header such as `X-Forwarded-For` or `CF-Connecting-IP`. With the wrong address, genuine crawlers fail verification and aren't counted.
</Warning>

## Your total traffic: `totals`

One entry per day with the total number of requests your site served — people and bots together. It's what lets the Overview show AI crawlers as a share of all your traffic. Nothing else about those requests is sent.

<ParamField body="day" type="string" required>
  The UTC day, as `YYYY-MM-DD`.
</ParamField>

<ParamField body="hits" type="integer" required>
  Requests served that day. Re-sending a day keeps the higher figure, so you can send a running total.
</ParamField>

## People arriving from an assistant: `referrals`

One entry per visit from someone who clicked a link in an AI assistant's answer. Only these three fields are sent — nothing about the visitor.

<ParamField body="source" type="string" required>
  Which assistant sent them. Use one of the identifiers below so it's labelled correctly.
</ParamField>

<ParamField body="path" type="string">
  The page they landed on. The query string is removed.
</ParamField>

<ParamField body="ts" type="string">
  When they arrived, in ISO 8601. Defaults to now.
</ParamField>

To decide whether a visit came from an assistant, check the request's **referrer** host and, because many links arrive without one, the **`utm_source`** parameter too (ChatGPT adds `utm_source=chatgpt.com` to its links):

| Referrer host or `utm_source` | Send as `source` |
| ----------------------------- | ---------------- |
| chatgpt.com, chat.openai.com  | `chatgpt`        |
| perplexity.ai                 | `perplexity`     |
| claude.ai                     | `claude`         |
| gemini.google.com             | `gemini`         |
| copilot.microsoft.com         | `copilot`        |
| you.com                       | `you`            |
| poe.com                       | `poe`            |
| chat.mistral.ai               | `mistral`        |
| grok.com                      | `grok`           |

## Limits

| Limit                      | Value                              |
| -------------------------- | ---------------------------------- |
| Crawler events per request | 1,000 — send more batches for more |
| Referrals per request      | 1,000                              |
| Requests per minute        | 120                                |
| How old a visit can be     | 7 days                             |
| How far in the future      | 10 minutes                         |

Visits outside the time window are skipped rather than rejected.

## The response

A successful request returns `200` with what was done with the batch:

```json theme={null}
{
  "accepted": 1,
  "verification": { "verified": 1, "pending": 0, "spoofed": 0, "unverifiable": 0, "unchecked": 0 },
  "referrals": 1,
  "visits": 0,
  "ignored": 0,
  "skipped": 0
}
```

<ResponseField name="accepted" type="integer">
  Crawler visits stored.
</ResponseField>

<ResponseField name="verification" type="object">
  How the stored visits fared in verification. `verified` came from the crawler's published addresses. `pending` is waiting on a DNS check, which runs after the request. `spoofed` used a crawler's name from an address that isn't its own. `unverifiable` is a crawler whose operator publishes nothing to check against. `unchecked` means no usable IP was sent — a private address, or none. Only verified visits are counted in the app.
</ResponseField>

<ResponseField name="referrals" type="integer">
  Arrivals from assistants stored.
</ResponseField>

<ResponseField name="visits" type="integer">
  Ordinary page views, sent only when the WordPress plugin is set to report every request. `0` otherwise.
</ResponseField>

<ResponseField name="ignored" type="integer">
  Events that weren't from a recognised AI crawler — usually people. Not stored.
</ResponseField>

<ResponseField name="skipped" type="integer">
  Events dropped for a bad timestamp, a time outside the window, or an invalid status code.
</ResponseField>

## Errors

| Status                             | Meaning                                                              |
| ---------------------------------- | -------------------------------------------------------------------- |
| `400`                              | The body isn't JSON, or has more than 1,000 events.                  |
| `401`                              | The key is missing, wrong or revoked.                                |
| `402` with `plan_required`         | The organization has no active plan. Nothing was stored.             |
| `402` with `monthly_limit_reached` | This month's visit allowance is used. Collection resumes next month. |
| `429`                              | More than 120 requests in a minute. Wait and retry.                  |

On a `402`, drop the batch rather than queueing it — it won't be accepted until the plan or month changes.

## Verification

Each crawler visit is checked against the addresses its operator publishes. Only verified visits appear in Agent Analytics. Crawlers whose operators publish nothing can't be verified, so their visits aren't counted — see [Recognised crawlers](/agent-analytics/crawlers).
