# Connect your game network to your storefront

> Send your game servers' status, market, leaderboards and players to Coritan, and deliver what your customers buy in game.

Source: https://www.coritan.com/docs/organizations/game-network/

If you run a game network, such as a group of Minecraft servers, the program that runs it can send Coritan what happens in game, and your storefront can show it: whether the network is up, how many play, the market's prices and the leaderboards. The same program reads what your customers buy and tells Coritan when each purchase reached the player, so the customer can see it on their order.

Your program sends and reads with an organization API key. Your website reads the reports without one.

## Before you begin

- Create an organization API key with the `network:write` scope on the **API keys** page, as [Create organization API keys](/docs/organizations/api-keys/) describes. It can send every report, record deliveries and read purchases. A key with `network:read` can only read purchases. Send the key in the `X-API-Key` header.
- Sell what the network delivers as **Standalone** products, which [Set up products and pricing](/docs/organizations/products-and-pricing/) covers. Your program only sees purchases of standalone products.
- Coritan keeps your network's public settings: the addresses players join on, your Discord invite, your vote sites and the characters your network puts before some players' names. Ask [support](https://www.coritan.com/dashboard/support) to set or change them. [What your website reads](#what-your-website-reads) lists them.

## Send the network's status

`PUT /api/v1/orgs/{org_slug}/network/status` replaces the status. Send it every 15 to 60 seconds:

```bash
curl -X PUT https://api.coritan.com/api/v1/orgs/acme/network/status \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"online": true, "players_online": 412, "max_players": 2000, "version_label": "1.21.x", "season": {"name": "Season 1", "started_at": "2026-10-17T14:00:00Z"}, "regions": [{"key": "eu", "name": "Europe", "players_online": 250}, {"key": "na", "name": "North America", "players_online": 162}]}'
```

| Field | Required | What it holds |
| --- | --- | --- |
| `online` | Yes | `true` while players can join. |
| `players_online` | Yes | Players online across the network, from 0 to 10,000,000. |
| `max_players` | No | How many players the network takes at once, or `null`. |
| `version_label` | No | The game versions players can join with, as your site shows them, such as `1.21.x`. Up to 40 characters. |
| `season` | No | The season running now, with its `name` (up to 80 characters) and when it `started_at`, or `null` between seasons. |
| `regions` | No | Players online in each region, up to 20, each with a `key` of your own, a `name` (up to 60 characters) and `players_online`. Leave it out for a network with one region. |

The answer is `{"kind": "status", "updated_at": "2026-10-17T14:00:05Z"}`, with the time Coritan saved the push. Times without an offset are read as UTC, and every time Coritan sends back is in UTC.

## Send the market

`PUT /api/v1/orgs/{org_slug}/network/market` replaces the market with the items you send, so an item you leave out leaves the market. Send it every one to five minutes:

```bash
curl -X PUT https://api.coritan.com/api/v1/orgs/acme/network/market \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"currency_symbol": "$", "items": [{"key": "diamond", "name": "Diamond", "category": "Ores", "sell": 92.5, "buy": 101, "change_24h": -2.5, "volume_24h": 1840}]}'
```

Each item has a `key` of your own and a `name` (up to 80 characters). It can also have:

- `category`, the group your site files it under, up to 40 characters.
- `sell`, what a player gets for selling one, and `buy`, what a player pays for one.
- `change_24h`, how far the price moved over the last 24 hours, in percent: `-2.5` for down 2.5%.
- `volume_24h`, how many changed hands over the last 24 hours.

Send `null`, or leave the field out, for what does not apply, such as `buy` for an item players cannot buy. A push holds up to 1,000 items, each `key` once. `currency_symbol` is what your site shows with prices, `$` when you leave it out. The answer is `{"kind": "market", "updated_at": ...}`.

## Send the leaderboards

`PUT /api/v1/orgs/{org_slug}/network/leaderboards` replaces every board. Send them every five to fifteen minutes:

```bash
curl -X PUT https://api.coritan.com/api/v1/orgs/acme/network/leaderboards \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"boards": [{"key": "richest", "title": "Richest players", "unit": "money", "entries": [{"rank": 1, "name": "Alex", "uuid": "5b0c0d4e-7f1a-4c2b-9d3e-1a2b3c4d5e6f", "value": 1250000}]}]}'
```

A board has a `key` of your own, a `title` (up to 60 characters), a `unit` and up to 100 `entries`. The `unit` says what each entry's `value` counts: `money`, `count`, `duration` in seconds, or `time` in milliseconds, for a best time such as a parkour run. Each entry has its `rank` from 1, a `name` (up to 40 characters), its `value`, and the player's `uuid`, or `null` for an entry that is not one player, such as a team.

A push holds up to 30 boards, each `key` once, in the order your site shows them. Coritan stores each board's entries in `rank` order. The answer is `{"kind": "leaderboards", "updated_at": ...}`.

## Record the players you see

`POST /api/v1/orgs/{org_slug}/network/players` records the players your network has seen, so your site can look a name up, for example to check the name a customer types when they buy something for a player. Send the players seen since your last request every 30 to 60 seconds, up to 1,000 a request:

```bash
curl -X POST https://api.coritan.com/api/v1/orgs/acme/network/players \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"players": [{"uuid": "5b0c0d4e7f1a4c2b9d3e1a2b3c4d5e6f", "name": "Alex", "edition": "java", "last_seen": "2026-10-17T14:02:11Z"}]}'
```

- `uuid` is the player's UUID, with or without dashes.
- `name` is the name the game shows, with the character your network puts before it, if any, such as `.Alex`: an optional `-` or `.`, then 1–16 letters, digits and underscores.
- `edition` is `java`, `java_offline` for a non-premium Java Edition account, or `bedrock`.
- `last_seen` is when the player was last on the network. A time in the future counts as now.

Coritan keeps one record for each `uuid`, with the name and edition of the latest sighting, so a player who changes their name is found by the new one. A sighting older than the one kept changes nothing. The answer is `{"saved": 1}`, the number of different players in the request.

## What your website reads {#what-your-website-reads}

These routes need no key and no sign-in. Their paths start with `/api/v1/orgs/{org_slug}`, as on the rest of this page:

| Route | What it returns |
| --- | --- |
| `GET /network/status` | The status as you last sent it, with the addresses players join on: `online`, `players_online`, `max_players`, `version_label`, `java_address`, `bedrock_address`, `bedrock_port`, `season` and `regions`. |
| `GET /network/market` | The `items` as you last sent them, the `categories` they use in the order they first appear, and `currency_symbol`. |
| `GET /network/leaderboards` | The `boards` as you last sent them. |
| `GET /network/players/lookup?name=Alex` | Whether a player with that name, in any case, has been seen. |

The status, the market and the leaderboards also carry `updated_at`, the time of your last push of that report, and `stale`. `stale` is `true` when the last push is more than five minutes old, so your site can say the figures may be out of date, for example while your network is down. Before your first push, a report has no figures yet: `updated_at` is `null`, `stale` is `true`, the lists are empty and the status's numbers are `null`. The status's addresses come from your network's settings, so they are there before the first push.

The lookup answers `{"found": true, "player": {"name": "Alex", "uuid": "5b0c0d4e-7f1a-4c2b-9d3e-1a2b3c4d5e6f", "edition": "java", "last_seen": "2026-10-17T14:02:11Z"}}` for the player seen most recently with that name, or `{"found": false}`. Its `name` takes the same form as a player's name above, and anything else answers `422`.

A push can take up to a minute to reach every visitor. Coritan keeps each answer for up to 15 seconds, and the `Cache-Control` header lets browsers and caches each keep it for 15 seconds and serve it for 15 more while they fetch a new one. An organization slug that does not exist, or whose organization is not active, answers `404 Organization not found`.

### Your network's settings

`GET /api/v1/orgs/{org_slug}/storefront/branding` carries your network's public settings in `network`. Until Coritan sets them, the answer has no `network`.

| Field | What it holds |
| --- | --- |
| `enabled` | Whether your site shows the network. |
| `java_address`, `bedrock_address`, `bedrock_port` | The addresses players join on, for each edition. |
| `discord_url` | Your Discord invite. |
| `vote_sites` | Up to 20 sites where players vote for your network, each with a `name` and a `url`. |
| `hosting_org` | The slug of another organization whose server hosting your site offers, or `null`. |
| `hosting_panel_url` | Where that organization's customers manage their servers. |
| `name_rules` | `offline_prefix` and `bedrock_prefix`: the character your network puts before the name of a non-premium Java Edition player and of a Bedrock Edition player, or `null` for none. |

A link that is not an `https://` address comes back as `null`.

## List the purchases to deliver

`GET /api/v1/orgs/{org_slug}/network/entitlements` lists your organization's services of standalone products in the order they last changed: by `updated_at`, then by `service_id`. It needs a key with `network:read` or `network:write`.

```bash
curl "https://api.coritan.com/api/v1/orgs/acme/network/entitlements?updated_after=2026-10-17T14:00:00Z&limit=100" \
  -H "X-API-Key: $ORG_API_KEY"
```

```json
{
  "entitlements": [
    {
      "service_id": 5012,
      "customer_id": 881,
      "product_id": 12,
      "product_slug": "gold-rank",
      "status": "active",
      "billing_cycle": "monthly",
      "config": {"minecraft_name": "Alex", "minecraft_edition": "java"},
      "next_due_date": "2026-11-17T14:03:00Z",
      "termination_date": null,
      "created_at": "2026-10-17T14:01:40Z",
      "updated_at": "2026-10-17T14:03:00Z",
      "delivery": null
    }
  ],
  "next_cursor": null
}
```

- `status` is where the purchase stands, such as `pending` until it is paid, `active`, `suspended` or `terminated`. Give the player what the purchase grants while it is `active`, and take it away when the purchase is `suspended` or `terminated`.
- `config` holds the details the customer gave when ordering, such as the player to deliver to, and the `delivery` you last recorded.
- `next_due_date` is when the next payment is due. `termination_date` is when a cancelled purchase ends, or `null`.
- `delivery` is the delivery you last recorded, or `null` before the first.

`updated_after` keeps the purchases that changed at or after a time, in ISO 8601. `limit` is how many come in a page: 100 unless you ask for 1–500. While `next_cursor` is not `null`, pass it back as `cursor` for the next page.

Any change to a purchase, such as a payment, a renewal, a cancellation or a delivery you record, gives it a new `updated_at`, so it comes again at the end of the list. To keep up, ask again every 15 to 60 seconds with `updated_after` set to a minute before the latest `updated_at` you have handled. A change can be saved a moment after the time it carries, and the minute's overlap catches it. You will see some purchases twice, so skip one whose `updated_at` you have already handled.

## Record a delivery

Tell Coritan how a delivery went with `POST /api/v1/orgs/{org_slug}/network/entitlements/{service_id}/delivery`:

```bash
curl -X POST https://api.coritan.com/api/v1/orgs/acme/network/entitlements/5012/delivery \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "delivered", "detail": "Your rank is active on every server."}'
```

- `status` is `delivered` once the player has it, `waiting` while it waits for them (until they next join, for example) or `failed` when it could not be given.
- `detail` is an optional sentence of up to 300 characters. The customer's account returns it with the purchase (`GET /api/v1/orgs/{org_slug}/portal/services/{service_id}`), so your storefront can show it: write it for the customer.

Coritan saves it in the purchase's `config.delivery` as `{"status": "delivered", "at": "2026-10-17T14:03:00Z", "detail": "Your rank is active on every server."}`, in place of the delivery recorded before. The answer is `{"entitlement": {...}}`, the purchase as the list shows it. Each delivery is in your organization's [audit log](/docs/organizations/audit-log/) as `service.delivery_recorded`, under the API key, with its `status` and `detail`.

## Result

Your website's reads return what your program sent, and `stale` turns `true` if it stops sending. Each purchase carries how its delivery went in `config.delivery`, and the audit log holds an entry for each delivery you record.

## Limits

| What | Limit |
| --- | --- |
| Size of one push | 2 MB |
| Regions in the status | 20 |
| Items in the market | 1,000 |
| Boards | 30, each with up to 100 entries |
| Players in one request | 1,000 |
| A price, a volume or a score | 1,000,000,000,000,000 either way |
| A delivery's `detail` | 300 characters |
| Purchases in a page | 500 |
| Requests with one key | The key's rate limit: 4,000 an hour unless you chose another |

The intervals on this page add up to fewer than 1,000 requests an hour, and one more for each delivery you record. The website's reads need no key, so they do not count.

## Troubleshooting

`403` with `scope_required`
: The key lacks the scope the route needs, and the message names it, such as `This API key needs the network:write scope.` You cannot add a scope to a key, so create a new key with it.

`404 Organization not found`
: The organization slug in the path is not the key's organization, or, on a read without a key, no active organization has that slug.

`404` with `not_found`
: The `service_id` is not a purchase of one of your organization's standalone products.

`413` with `payload_too_large`
: The push is larger than 2 MB. Send fewer items or players in each request.

`422` with a `detail` list
: A field is wrong. Each entry names it in `loc`, such as `["body", "items", 0, "sell"]`, with the reason in `msg`: two items with one `key`, a number that is `NaN` or `Infinity`, an `edition` that is not one of the three, a `limit` outside 1–500. Coritan saved nothing from the request.

`422` with `invalid` and `"field": "cursor"`
: The `cursor` is not one the list gave out. Pass the `next_cursor` of the page before, or leave it out and start again from `updated_after`.

`stale` stays `true`
: No push of that report has arrived in the last five minutes. Check that your program is running and that its pushes answer `200`.

A missing, wrong or revoked key, an address outside the key's allow-list and a key past its rate limit answer as [Create organization API keys](/docs/organizations/api-keys/#troubleshooting) describes.

## Next steps

- [Create organization API keys](/docs/organizations/api-keys/): the key your program uses, its scopes and its rate limit.
- [Set up products and pricing](/docs/organizations/products-and-pricing/): the standalone products your network delivers.
- [Read the organization audit log](/docs/organizations/audit-log/): where each delivery shows up.
- [Show your catalogue with the Storefront API](/docs/organizations/storefront/storefront-api/): the rest of what your website reads.

## API

- `PUT /api/v1/orgs/{org_slug}/network/status`: Replaces the network's status (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-put-api-v1-orgs-org-slug-network-status)
- `PUT /api/v1/orgs/{org_slug}/network/market`: Replaces the market (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-put-api-v1-orgs-org-slug-network-market)
- `PUT /api/v1/orgs/{org_slug}/network/leaderboards`: Replaces the leaderboards: up to 30 boards of up to 100 entries each (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-put-api-v1-orgs-org-slug-network-leaderboards)
- `POST /api/v1/orgs/{org_slug}/network/players`: Records players the network has seen, so the website can look a name up (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-post-api-v1-orgs-org-slug-network-players)
- `GET /api/v1/orgs/{org_slug}/network/status`: The network's status as the backend last pushed it, with the addresses players join on (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-get-api-v1-orgs-org-slug-network-status)
- `GET /api/v1/orgs/{org_slug}/network/market`: The market as the backend last pushed it (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-get-api-v1-orgs-org-slug-network-market)
- `GET /api/v1/orgs/{org_slug}/network/leaderboards`: The leaderboards as the backend last pushed them, each board's entries in rank order (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-get-api-v1-orgs-org-slug-network-leaderboards)
- `GET /api/v1/orgs/{org_slug}/network/players/lookup`: Whether the network has seen a player with this name (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-get-api-v1-orgs-org-slug-network-players-lookup)
- `GET /api/v1/orgs/{org_slug}/network/entitlements`: Lists the purchases the network delivers, in the order they last changed (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-get-api-v1-orgs-org-slug-network-entitlements)
- `POST /api/v1/orgs/{org_slug}/network/entitlements/{service_id}/delivery`: Records how a purchase's delivery went (https://www.coritan.com/docs/api/reference/organizations/org-game-network/#op-post-api-v1-orgs-org-slug-network-entitlements-service-id-delivery)
