WardenCoredocs
API

API Reference

The WardenCore public REST API — authentication, errors, and conventions.

The WardenCore API lets your own tools, bots, and scripts drive a server programmatically — read players, run commands, manage the economy, and more.

Base URL

All requests go to https://api.wardencore.ru/v1. Every request must be made over HTTPS.

Premium feature

The API is available on servers with an active premium plan. Creating a key or calling /v1 on a non-premium server returns 403 premium_required, and if a server's premium lapses its existing keys stop working until it's renewed.

Authentication

The API authenticates with a per-server API key. Create one in the panel under Settings → API (see API Keys) — the key is shown once, so copy it immediately.

Send it as a bearer token:

Authorization: Bearer wc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

A key is bound to exactly one server and carries a fixed set of scopes (permissions). A request that needs a scope the key doesn't have returns 403 insufficient_scope.

A key can also be restricted to specific source IPs or CIDR ranges (IPv4/IPv6) in the panel. A request from an IP outside a restricted key's allowlist returns 403 ip_not_allowed.

Response format & errors

Successful responses are JSON with snake_case fields. Errors always use one stable envelope:

{
  "error": {
    "code": "insufficient_scope",
    "message": "The API key lacks the required scope: players:write.",
    "details": { "missing_scope": "players:write" },
    "request_id": "req_9f2c…"
  }
}

Always branch on the machine-readable code, never the human-readable message. Include the request_id when contacting support.

Response shape

  • A single resource is returned as a bare JSON object (no wrapper).
  • A collection is returned as { "data": [...], "has_more": boolean, "next_cursor": string | null } (see Pagination).
  • A mutation returns the affected resource (bare), or 204 No Content when there is nothing to return; a freshly created resource comes back as 201 Created.
  • Fields are never omitted. An absent value is null, not a missing key — so you can parse a stable shape. New fields may be added within v1; ignore ones you don't recognize.

Number handling

Integer fields are JSON numbers. A value that can exceed 2⁵³−1 (large identifiers and lifetime counters) is sent as a decimal string instead, documented per field — treat those as opaque. Coordinates and other fractional values are ordinary JSON numbers.

Request limits

Request bodies must be sent as Content-Type: application/json and are limited to 1 MB. A larger body returns 413 payload_too_large; any other content type returns 415 unsupported_media_type.

CodeStatusMeaning
unauthorized401Missing or invalid API key.
premium_required403The server does not have an active premium plan.
insufficient_scope403The key lacks a required scope.
ip_not_allowed403The key is restricted to other source IPs.
validation_error400The request body or query failed validation.
not_found404The resource (or route) does not exist.
rate_limited429Too many requests — see rate limits.
idempotency_conflict409An Idempotency-Key was reused with a different body.
conflict409The request conflicts with the current state of the resource.
precondition_failed412An If-Match precondition failed — the resource changed.
payload_too_large413The request body exceeds the size limit.
unsupported_media_type415The request body must be sent as application/json.
server_unavailable503The game server is offline or unreachable. Retry later.
gateway_timeout504The game server did not respond in time.

Rate limits

Limits are applied per API key. Every response carries the IETF draft headers:

RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 42
RateLimit-Policy: 120;w=60

When you exceed the limit you get 429 rate_limited with a Retry-After header (seconds), in the standard error envelope.

Two layers of 429

The per-key limit above is enforced by the application and always returns the JSON rate_limited envelope plus the RateLimit-* and Retry-After headers. A separate coarse per-IP limit at the edge (nginx) guards against floods before the request reaches the application — when it rejects you, the 429 may be a bare response with no JSON envelope and no Retry-After. Treat any 429 as "back off and retry later"; branch on the status code, and only rely on the envelope/headers when they're present.

Pagination

List endpoints are cursor-paginated. Responses carry has_more and next_cursor; pass the opaque next_cursor back as the cursor query parameter to fetch the next page. Do not construct cursors yourself — a cursor is an opaque token that encodes only your pagination position, so tampering with it can only move you within your own results, never widen what you can access.

Idempotency

Send an Idempotency-Key header on POST/PUT requests to make retries safe. Reusing the same key replays the original response instead of executing the action twice; reusing it with a different body returns 409 idempotency_conflict.

Idempotency-Key: 3f9a1c7e-…

Caching

Read endpoints that are safe to cache return a strong ETag and a Cache-Control header (e.g. private, max-age=30). To make a conditional request, send the ETag back as If-None-Match on the next call — if nothing changed, the API replies 304 Not Modified with an empty body, so you reuse your cached copy and save bandwidth:

GET /v1/… HTTP/1.1
Authorization: Bearer wc_live_…
If-None-Match: "9f2c8ab1…"
  • Cache-Control: private means the response is scoped to your key — never store it in a shared/CDN cache. A stale-while-revalidate directive, when present, tells your cache how long it may serve the stale copy while it refreshes in the background.
  • A weak validator (If-None-Match: W/"…") is accepted (RFC 9110 weak comparison).
  • Live-status endpoints (e.g. GET /v1/server) are intentionally not cached — they always reflect the current state.

Concurrency control

To avoid lost updates when several clients write the same resource, use optimistic concurrency. A GET returns the resource's current ETag; send it back as If-Match on your next write:

PUT /v1/… HTTP/1.1
Authorization: Bearer wc_live_…
If-Match: "9f2c8ab1…"

If the resource changed since that ETag, the write is rejected with 412 precondition_failed and nothing is modified — re-fetch, reconcile, and retry. If-Match uses strong comparison (RFC 9110), so a weak validator never satisfies it. Omitting If-Match skips the check (last-write-wins).

Versioning & deprecation

The API is versioned in the path (/v1). Breaking changes ship under a new version; within a version we only ever add fields, so parse defensively and ignore unknown ones. Every response carries an API-Version header.

When an endpoint or a whole version is deprecated, its responses carry a Deprecation header — an RFC 9745 date in Structured-Field form (@<unix-seconds>) marking when it was deprecated. Once a removal date is set they also carry a Sunset header (an HTTP-date, RFC 8594). Watch for both and migrate before the sunset date.

Server availability

The API proxies to the WardenCore mod on your server over a live connection. If the server is offline, requests that need it return 503 server_unavailable with a Retry-After header — retry once the server is back online.

The API is off until an operator enables it

The public API ships disabled. Until the server operator turns it on, every /v1 request answers 404 not_found, the same for every endpoint and every key.

That looks like an ordinary miss, so check the scope: a disabled API 404s paths you know exist, while a real not-found — an unknown player ref, say — comes from an endpoint that answers other requests normally.

Endpoints

The full endpoint reference is published per release as we roll endpoints out. This page documents the conventions every endpoint follows.

On this page