straywild-discovery-server/docs/API.md

7.2 KiB

Discovery API v1

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.

All request and response bodies use application/json. Production clients must use HTTPS.

GET /health

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.

GET /v1/rooms

Lists active rooms. Optional exact-match filters:

  • game_version
  • protocol_version

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.

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.

{
  "room_name": "Pond Friends",
  "port": 7777,
  "current_players": 0,
  "max_players": 8,
  "game_version": "0.20.3-alpha",
  "protocol_version": 12,
  "host_kind": "player",
  "connection_mode": "relay"
}

current_players may be zero for an empty dedicated server. A player-hosted 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.

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.

The service applies configured global and per-observed-address active-room limits. Exceeding one returns 429 room_limit; expired leases stop counting automatically.

The discovery release accepts room advertisements only from the matching straywild game release. A mismatched game_version returns 409 game_version_mismatch with required_game_version; the room is not created or updated.

PUT /v1/rooms/{room_id}

Renews and updates a room lease. Send the complete room payload and:

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.

POST /v1/rooms/{room_id}/join-attempts

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.

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.

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, live straywild session.

Error shape

{
  "error": {
    "code": "invalid_room",
    "message": "port must be between 1 and 65535"
  }
}

Trust boundary

  • 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.
  • 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.
  • 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.
  • 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.