const string = { type: "string" }; const date = { type: "string", format: "date-time" }; const id = { type: "string", format: "uuid" }; const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` }); const pagination = [ { name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 50, default: 20 }, }, { name: "offset", in: "query", schema: { type: "integer", minimum: 0, maximum: 10000, default: 0 }, }, ]; const success = (schema: unknown) => ({ description: "Success", content: { "application/json": { schema } }, }); const errors = Object.fromEntries( [ [400, "Invalid query or map ID"], [401, "Missing, invalid, expired or revoked key"], [403, "Required permission missing"], [404, "Resource or endpoint not found"], [429, "Quota exceeded; wait Retry-After seconds"], [503, "Temporarily unavailable"], ].map(([status, description]) => [ status, { description, content: { "application/json": { schema: ref("Error") } } }, ]), ); const list = (name: string, item: string) => ({ type: "object", required: [name, "pagination"], properties: { [name]: { type: "array", items: ref(item) }, pagination: ref("Pagination"), }, }); const operation = ( summary: string, scope: string, schema: unknown, parameters: unknown[] = [], ) => ({ summary, description: `Requires the ${scope} key permission.`, "x-required-scope": scope, security: [{ DeveloperKey: [] }], parameters, responses: { 200: success(schema), ...errors }, }); export const developerOpenApi = { openapi: "3.1.0", info: { title: "MineGeo Developer API", version: "1.0.0", description: "Read-only HTTPS API. Bearer keys are managed at /dev/api. 60 requests per key per fixed minute; 180 requests per account per minute shared across keys. All authenticated requests, including validation/permission errors, consume quota. A 429 response includes Retry-After; successful authorized responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until reset). X-RateLimit-Reset is a Unix timestamp. Requests with invalid keys cannot access account data. No upload, publication, gameplay writes or moderator access is granted.", }, servers: [{ url: "/api/dev/v1" }], paths: { "/maps": { get: operation("List published maps", "maps:read", list("maps", "Map"), [ ...pagination, { name: "q", in: "query", schema: { type: "string", maxLength: 80 }, description: "Case-insensitive map-name search", }, ]), }, "/maps/{id}": { get: operation( "Get a published map", "maps:read", { type: "object", required: ["map"], properties: { map: ref("Map") }, }, [{ name: "id", in: "path", required: true, schema: id }], ), }, "/me": { get: operation("Get the key owner's profile", "account:read", { type: "object", required: ["account"], properties: { account: ref("Account") }, }), }, "/me/maps": { get: operation( "List the owner's maps, including private maps", "account:maps:read", list("maps", "Map"), [ ...pagination, { name: "q", in: "query", schema: { type: "string", maxLength: 80 } }, ], ), }, "/me/games": { get: operation( "List the owner's solo-game history", "account:games:read", list("games", "Game"), pagination, ), }, }, components: { securitySchemes: { DeveloperKey: { type: "http", scheme: "bearer", bearerFormat: "mg_dev_<64 hex characters>", }, }, schemas: { Error: { type: "object", required: ["error"], properties: { error: { type: "object", required: ["code", "message"], properties: { code: string, message: string }, }, }, }, Pagination: { type: "object", properties: { limit: { type: "integer" }, offset: { type: "integer" }, total: { type: "integer" }, nextOffset: { type: ["integer", "null"] }, }, }, Map: { type: "object", properties: { id, name: string, description: string, source: string, state: string, bounds: { type: "object", properties: { minX: { type: "number" }, minZ: { type: "number" }, maxX: { type: "number" }, maxZ: { type: "number" }, }, }, panoramaCount: { type: "integer" }, createdAt: date, updatedAt: date, url: { type: "string", description: "Relative MineGeo map page path; private maps still require their owner's browser login.", }, }, }, Account: { type: "object", properties: { id, displayName: string, createdAt: date, appearance: { type: "object", properties: { nameGlow: { type: "boolean" }, glowColor: string, bugFinder: { type: "boolean" }, customPrefix: { type: ["string", "null"] }, customPrefixIcon: string, }, }, }, }, Game: { type: "object", properties: { id, difficulty: { type: "string", enum: ["easy", "normal", "hard"] }, createdAt: date, currentRound: { type: "integer", minimum: 0, maximum: 5 }, complete: { type: "boolean" }, score: { type: ["integer", "null"], minimum: 0, maximum: 25000, description: "Final score only; null until a complete five-round result is available.", }, }, }, }, }, };