# API

Sro.gg publishes its server directory as JSON so you do not have to crawl the site. Everything here is public, needs no key, and may be reused under the terms on the [data methodology](https://sro.gg/en/page/data-methodology) page: attribute sro.gg and link to the page you took the data from.

All endpoints share one limit of **10 requests per minute per IP address**. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; going over returns `429` with a `Retry-After` header. The limit is shared, so spreading requests across endpoints does not raise it.

## Identifying yourself

Send a `User-Agent` header that says who you are and why you are calling. This matters most for AI agents and scripts: without it you arrive as an anonymous request, and we cannot tell a research tool from a scraper.

```
Name/version (+contact; purpose=what-you-use-it-for)
```

```
AcmeBot/1.0 (+https://acme.com/bot; purpose=price-comparison)
ResearchAgent/2.1 (+mailto:you@example.com; purpose=academic research)
```

The contact is a URL or a `mailto:` address, prefixed with `+`. The purpose is a short phrase describing the use, not a sentence. Both parts are required — a name on its own is not a declaration and will still be treated as unidentified.

Clients we cannot identify — `curl`, bare HTTP libraries, anything with no recognisable agent — receive a `notice` field at the top of every response asking for this. Declare yourself and the notice disappears. Well-known crawlers such as Googlebot, GPTBot and ChatGPT-User are recognised automatically and never see it, though they may still send a `purpose`.

Identifying yourself costs nothing and does not change your rate limit today. It means that when we look at who uses the API, you appear as yourself rather than as an anonymous IP address — and if we ever need to contact you about your usage, or raise a limit for you, we can.

Unidentified traffic is the traffic we can least account for. A single IP address sending a sustained volume of anonymous `curl`-style requests may be throttled or blocked, because from our side it is indistinguishable from scraping. Declared clients are not treated this way: we can see what they are for, and we would reach out before acting. If you are automating anything against this API, sending the header is the cheapest way to stay on the right side of that line.

## The server directory

```
GET https://sro.gg/api/public/servers
```

Returns every listed server with its level cap, race configuration, player limits, launch dates and last known player count. This is the endpoint to use for questions about population, features or availability.

Each entry carries `playersOnline` together with `playersOnlineAt`, the moment that reading was taken. Always publish the timestamp alongside the number. A `null` count means no counter could be read, not zero players.

### Filters

- `status` — lifecycle states, comma separated. Accepts `live`, `beta`, `upcoming` and `inactive`. Example: `?status=live,beta`
- `cap` — one level cap. Example: `?cap=110`
- `openedAfter` — servers that opened on or after this date. Example: `?openedAfter=2026-07-15`
- `openedBefore` — servers that opened on or before this date
- `sort` — `playersOnline`, `opensAt` or `likes`

- `locale` — `en` or `tr`. Picks the language of `name` and `description`, and points `url` at that locale. `urls` always carries every locale regardless.

Closed servers are excluded unless you ask for them with `status=inactive` or `includeInactive=true`. They stay listed for reference and are never places to play.

Combining filters covers the site's own listing pages: `?status=upcoming&sort=opensAt` is the upcoming calendar, and `?openedAfter=` with a date a month back is the new-servers page.

### Invalid parameters

A known parameter carrying a value outside its allowed set returns `400` — it is never silently ignored, and you never receive an unfiltered list by mistake:

```json
{
  "error": {
    "code": "invalid_query_parameter",
    "parameter": "status",
    "received": "invalid",
    "allowed": "live, beta, upcoming, inactive (comma separated)",
    "documentation": "https://sro.gg/en/page/api"
  }
}
```

A parameter we do not recognise returns `400` as well, so a typo like `staus=live` fails loudly instead of quietly returning the unfiltered directory:

```json
{
  "error": {
    "code": "unknown_query_parameter",
    "parameter": "staus",
    "allowed": ["status", "cap", "openedAfter", "openedBefore", "sort", "includeInactive", "locale"],
    "documentation": "https://sro.gg/en/page/api"
  }
}
```

Nothing outside that list is accepted — tracking parameters such as `utm_source` or `ref` are rejected too. If you get a `200`, your filters were applied as written.

## Trending

```
GET https://sro.gg/api/public/trending
```

Servers ranked by how many times their sro.gg page was viewed over the last seven days, with `growthPercent` comparing that against the seven days before.

## Hype

```
GET https://sro.gg/api/public/hype
```

Servers ranked by how many people are reading their sro.gg page right now, measured over a 30 minute window.

## What trending and hype are not

Both endpoints measure **traffic on sro.gg, not players in the game**. They answer "which servers are people looking at", never "which servers are the biggest". A server can trend because it just launched, because it was mentioned somewhere, or because it closed.

Do not republish these figures as player counts. For population use the directory endpoint, where every number carries the timestamp of its reading.

## Mirrors outside /api/

Some fetchers and agent tools refuse to open any path beginning with `/api/`, regardless of what `robots.txt` allows. Every endpoint above is therefore also served from a plain `.json` path:

```
GET https://sro.gg/data/servers.json
GET https://sro.gg/data/trending.json
GET https://sro.gg/data/hype.json
```

These are the same handlers, so the payload, the filters, the rate limit and the cache headers are identical — `https://sro.gg/data/servers.json?status=live&cap=110` behaves exactly like the `/api/public/servers` equivalent. Use whichever your client can open; there is no reason to prefer one over the other otherwise.

## Caching

The directory and trending are cached for 15 minutes, hype for 60 seconds. The whole directory arrives in a single request and changes slowly, so caching one response beats polling. If you need a higher limit, get in touch before working around this one.

## Markdown

Every page under `/page/` is also served as raw markdown by appending `.md` to its URL — for example [/page/api.md](https://sro.gg/en/page/api.md). That is the source these pages are rendered from, without navigation or markup around it.
