Files
Rendezvous/docs/contracts/http-v1.md
KyuubiYoru 69c8b2d2bc
quality-gate / quality (push) Successful in 51s
feat: freeze v1 transport contracts (#4)
Closes #4
2026-07-16 04:52:38 +02:00

4.8 KiB
Raw Permalink Blame History

HTTP contract v1

Tracking: #4

All production endpoints require HTTPS. JSON uses UTF-8, camel-case property names, compact output, string-valued camel-case enums, and ISO 8601 timestamps. Every request that contains a body carries contractVersion: 1; browse carries the same value as a required query parameter.

Compatibility and parsing

  • Contract version matching is exact. Any value other than 1 fails with unsupportedContractVersion; it is never guessed or downgraded.
  • Gameplay protocol matching is exact. buildVersion is display and diagnostic text only and never decides compatibility.
  • Unknown JSON object properties are ignored so additive v1 responses remain readable. Unknown enum names, numeric enum values, comments, trailing commas, invalid identifier strings, and excessive nesting are rejected.
  • Game, environment, and region IDs are lowercase URL-safe slugs. Listing, lease, join-attempt, and mediation IDs are non-empty UUIDs serialized as JSON strings.
  • Clients must honor request cancellation. A disconnected or cancelled request does not promise a response body; the server should stop work where safe.

Endpoints

Method Path Purpose
POST /v1/sessions Register a session and create its renewable lease.
POST /v1/sessions/{listingId}/renew Renew the listing lease.
PUT /v1/sessions/{listingId} Replace mutable browser fields and capacity.
DELETE /v1/sessions/{listingId} Withdraw a listing.
GET /v1/sessions Browse compatible public sessions.
GET /v1/sessions/{listingId} Resolve a public or explicitly shared unlisted listing.
POST /v1/join-attempts Authorize and create a short-lived join attempt.
GET /v1/sessions/{listingId}/join-attempts Let an authenticated host poll pending attempts.
POST /v1/join-attempts/{attemptId}/outcome Report a bounded connection outcome.
GET /health/live Report that the HTTP process is alive.
GET /health/ready Report whether the UDP mediator is bound and ready.

The generated OpenAPI document is the normative shape reference for parameters, bodies, and responses. Contract-only endpoints return 501 until their behavior is implemented by the subsequent directory, lease, and join-orchestration issues.

Host polling sends its reusable lease credential in X-Rendezvous-Lease-Token; it must never be placed in a URL. Lease credentials for mutation operations are carried in their request bodies. Public browser responses contain no IP endpoints, lease tokens, punch capabilities, connection tickets, player identifiers, or gameplay state.

Idempotency, cursors, and retries

Registration and join creation require a caller-generated visible-ASCII idempotencyKey. A repeat in the same authorization scope returns the original result while the key is retained; reusing a key with a different payload fails with conflict. Keys are opaque and must not contain credentials.

Cursors are opaque, endpoint-specific, short-lived values. A client may echo a cursor only to the endpoint and filters that produced it. Invalid or expired cursors fail with invalidRequest; clients restart browsing from the first page. Renew, update, delete, and outcome reporting are safe to retry with the same lease/attempt identity after a transport-level failure.

Limits

Limits are measured after UTF-8 encoding where stated. Servers reject the entire request rather than truncate values.

Item v1 limit
HTTP request body 16 KiB
Browser response body 256 KiB
Browser page 100 listings
Metadata document 4 KiB, 32 keys
Metadata key / value 64 / 256 UTF-8 bytes
Game / environment / region ID 64 / 32 / 32 characters
Display name / build version 128 / 64 UTF-8 bytes
Idempotency key 64 visible ASCII characters
Cursor 512 visible ASCII characters
Diagnostic code 64 visible ASCII characters
Error message 256 UTF-8 bytes
Reusable HTTP credential 1,024 characters
Session capacity 110,000 players

Error mapping

Errors use ApiError with a stable code, bounded safe message, optional correlationId, and optional retryAfterSeconds. Messages are diagnostic and must not be parsed. Secrets and raw credentials are never echoed.

HTTP Codes
400 invalidRequest, unsupportedContractVersion
401 authenticationRequired
403 forbidden
404 notFound
409 conflict, incompatibleProtocol, replayRejected, capacityExceeded
410 expired, staleHost
429 rateLimited (with retry guidance when known)
503 serviceUnavailable (with retry guidance when known)
500 internalError

Malformed input must receive the same bounded error family regardless of which parser or validation stage rejected it.