2026-08-10 10:19:57 -04:00
|
|
|
# Discovery API v1
|
|
|
|
|
|
2026-09-01 10:21:34 -04:00
|
|
|
The API stores public room advertisements as short-lived leases and coordinates UDP hole punching.
|
|
|
|
|
Player hosts explicitly advertise either direct or relay routing; dedicated servers must advertise
|
|
|
|
|
direct routing. Public room and social-presence responses never contain a host address or port.
|
|
|
|
|
Relay-enabled join responses contain only the relay endpoint.
|
2026-08-10 10:19:57 -04:00
|
|
|
|
|
|
|
|
All request and response bodies use `application/json`. Production clients must use HTTPS.
|
|
|
|
|
|
|
|
|
|
## `GET /health`
|
|
|
|
|
|
2026-09-01 10:21:34 -04:00
|
|
|
Returns service status, package version, configured build revision, active room count, whether the
|
|
|
|
|
relay is enabled, and the active relay allocation count. Deployments should set
|
|
|
|
|
`straywild_DISCOVERY_BUILD_REVISION` to the exact Git commit they are running.
|
2026-08-10 10:19:57 -04:00
|
|
|
|
|
|
|
|
## `GET /v1/rooms`
|
|
|
|
|
|
|
|
|
|
Lists active rooms. Optional exact-match filters:
|
|
|
|
|
|
|
|
|
|
- `game_version`
|
|
|
|
|
- `protocol_version`
|
|
|
|
|
|
2026-09-01 10:21:34 -04:00
|
|
|
The response contains `rooms` and the server's `ttl_seconds`. Each room includes metadata plus
|
|
|
|
|
either `connection_mode: "relay"` with `ip_privacy: "relayed"`, or an explicitly configured
|
|
|
|
|
`direct` / `peer_visible` pair. It intentionally omits `address` and `port`.
|
2026-08-10 10:19:57 -04:00
|
|
|
|
|
|
|
|
## `POST /v1/rooms`
|
|
|
|
|
|
|
|
|
|
Creates a room lease. The service derives the advertised address from the connection rather than
|
|
|
|
|
trusting an address supplied by the game client.
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"room_name": "Pond Friends",
|
|
|
|
|
"port": 7777,
|
2026-08-10 13:56:03 -04:00
|
|
|
"current_players": 0,
|
2026-08-10 10:19:57 -04:00
|
|
|
"max_players": 8,
|
2026-09-01 10:21:34 -04:00
|
|
|
"game_version": "0.20.3-alpha",
|
|
|
|
|
"protocol_version": 12,
|
|
|
|
|
"host_kind": "player",
|
|
|
|
|
"connection_mode": "relay"
|
2026-08-10 10:19:57 -04:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-08-10 13:56:03 -04:00
|
|
|
`current_players` may be zero for an empty dedicated server. A player-hosted
|
2026-09-01 10:21:34 -04:00
|
|
|
room normally includes its host in this count. `host_kind` accepts `player` or
|
|
|
|
|
`dedicated`. Player rooms may request `direct` or `relay`; dedicated rooms must
|
|
|
|
|
request `direct`. A dedicated relay advertisement, or a relay advertisement
|
|
|
|
|
when relay service is unavailable, is rejected with `400 invalid_room`.
|
2026-08-10 13:56:03 -04:00
|
|
|
|
2026-08-10 19:49:47 -04:00
|
|
|
The `201` response includes the room, a secret `lease_token`, and an endpoint verification token.
|
|
|
|
|
The host sends the verification token to the UDP rendezvous from its bound ENet socket. Rooms are
|
|
|
|
|
excluded from public listings until the service observes that endpoint.
|
2026-08-10 10:19:57 -04:00
|
|
|
|
|
|
|
|
The service applies configured global and per-observed-address active-room limits. Exceeding one
|
|
|
|
|
returns `429 room_limit`; expired leases stop counting automatically.
|
|
|
|
|
|
2026-08-25 17:14:33 -04:00
|
|
|
The discovery release accepts room advertisements only from the matching straywild game release.
|
2026-08-11 20:31:02 -04:00
|
|
|
A mismatched `game_version` returns `409 game_version_mismatch` with `required_game_version`; the
|
|
|
|
|
room is not created or updated.
|
|
|
|
|
|
2026-08-10 10:19:57 -04:00
|
|
|
## `PUT /v1/rooms/{room_id}`
|
|
|
|
|
|
|
|
|
|
Renews and updates a room lease. Send the complete room payload and:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
Authorization: Bearer <lease_token>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Hosts should heartbeat well before the configured TTL, initially every 15 seconds for a 45-second
|
|
|
|
|
lease. A missing heartbeat causes automatic removal without requiring a disconnect callback.
|
|
|
|
|
|
|
|
|
|
## `DELETE /v1/rooms/{room_id}`
|
|
|
|
|
|
|
|
|
|
Removes a room immediately. Requires the same bearer token. A clean host shutdown should call this,
|
|
|
|
|
but TTL expiry remains authoritative for crashes and lost connectivity.
|
|
|
|
|
|
2026-08-10 19:49:47 -04:00
|
|
|
## `POST /v1/rooms/{room_id}/join-attempts`
|
|
|
|
|
|
2026-09-01 10:21:34 -04:00
|
|
|
Creates a short-lived join capability for a public room. For a relay route, the joining game sends
|
|
|
|
|
the capability to its allocated relay from the same ENet socket used for gameplay. The allocation
|
|
|
|
|
then accepts traffic only from that authenticated client socket and the verified host endpoint.
|
|
|
|
|
For an explicitly configured direct route, the capability retains the older rendezvous behavior.
|
|
|
|
|
|
|
|
|
|
The `201` response also contains a short-lived `route` with `transport`, `address`, `port`, and
|
|
|
|
|
`ip_privacy`. For a relay room, these are the relay's public address and unique allocation port;
|
|
|
|
|
the host endpoint is never returned. For a direct room, this is the only unauthenticated response
|
|
|
|
|
that discloses the verified host endpoint, and it is issued only after an explicit Join. Responses
|
|
|
|
|
are marked `Cache-Control: no-store`. Join issuance is limited per source address, per room,
|
|
|
|
|
globally, and by concurrent pending count. A relay allocation failure returns
|
|
|
|
|
`503 relay_unavailable`; it never falls back to direct.
|
2026-08-10 19:49:47 -04:00
|
|
|
|
|
|
|
|
## `GET /v1/rooms/{room_id}/join-attempts`
|
|
|
|
|
|
|
|
|
|
Requires the room lease bearer token. It consumes observed joining endpoints so the host can send
|
|
|
|
|
same-socket UDP punch packets before ENet retries its connection.
|
|
|
|
|
|
2026-08-23 20:52:57 -04:00
|
|
|
## Live friend presence and invitations
|
|
|
|
|
|
|
|
|
|
These endpoints are an ephemeral capability channel, not an account or social-graph service. The
|
|
|
|
|
server does not receive player identity fingerprints or a complete friend list, writes nothing to
|
|
|
|
|
a database, and discards presence and invitations after short timeouts.
|
|
|
|
|
|
|
|
|
|
`POST /v1/presence` publishes or removes the caller's status under one or more secret write
|
|
|
|
|
capabilities. `POST /v1/presence/query` reads the corresponding hashed channels. A published room
|
|
|
|
|
is returned only while it remains a verified, version-compatible public room.
|
|
|
|
|
|
|
|
|
|
`POST /v1/invitations/poll` marks the supplied capability inboxes as currently reachable and
|
|
|
|
|
consumes any live invitations. `POST /v1/invitations` sends a public-room invitation to one inbox.
|
|
|
|
|
The send fails with `409 friend_offline` and `This person needs to be online to do this.` unless
|
|
|
|
|
that inbox was polled recently. There is no offline delivery.
|
|
|
|
|
|
|
|
|
|
All four requests include the normal `game_version` and `protocol_version`. Capability values are
|
|
|
|
|
64 lowercase hexadecimal characters and must be exchanged by the games during an authenticated,
|
2026-08-25 17:14:33 -04:00
|
|
|
live straywild session.
|
2026-08-23 20:52:57 -04:00
|
|
|
|
2026-08-10 10:19:57 -04:00
|
|
|
## Error shape
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"error": {
|
|
|
|
|
"code": "invalid_room",
|
|
|
|
|
"message": "port must be between 1 and 65535"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Trust boundary
|
|
|
|
|
|
2026-09-01 10:21:34 -04:00
|
|
|
- The UDP rendezvous observation is authoritative for a room's private join address and port.
|
|
|
|
|
- Public browse, presence, and invitation payloads never contain endpoints.
|
|
|
|
|
- Endpoint-bearing join routes are short-lived and must never be shown, logged, or persisted by
|
|
|
|
|
clients. Relay routes contain no peer endpoint.
|
2026-08-10 10:19:57 -04:00
|
|
|
- `X-Forwarded-For` is honored only when the immediate peer belongs to an explicitly configured
|
|
|
|
|
trusted proxy CIDR.
|
|
|
|
|
- Lease tokens authorize update and deletion but are never included in public listings.
|
2026-08-23 20:52:57 -04:00
|
|
|
- Friend capability channels are short-lived and reveal neither identity fingerprints nor a full
|
|
|
|
|
social graph to the service.
|
|
|
|
|
- The service is an ephemeral directory, not a source of gameplay or friendship authority.
|
2026-09-01 10:21:34 -04:00
|
|
|
- The service emits no HTTP request/access log. It does not log raw addresses, ports,
|
|
|
|
|
address-derived pseudonyms, request paths tied to rooms, names, payloads, headers, tokens,
|
|
|
|
|
authentication material, or friendship capabilities. Unexpected request failures produce only
|
|
|
|
|
a generic operational error without peer or request metadata.
|
|
|
|
|
- Peer endpoints exist only in volatile room, join, abuse-limit, and relay state for as long as
|
|
|
|
|
needed to provide the service. They are never written to an application log or database.
|
|
|
|
|
- A production reverse proxy must have access logging disabled for all discovery routes. Default
|
|
|
|
|
proxy logs commonly contain raw client addresses and violate this service's privacy policy.
|
|
|
|
|
- The relay necessarily holds peer endpoints in volatile memory while an allocation is active.
|
|
|
|
|
Allocations require a capability from the joining ENet socket, accept host traffic only from the
|
|
|
|
|
verified endpoint, enforce traffic budgets, and expire when idle.
|