{"openapi":"3.1.0","info":{"title":"Modly Developer API","version":"1.0.0","description":"Per-server API. A server admin creates keys in the dashboard under “Developer API”; every key belongs to exactly one server. MCP is available separately at /api/mcp. All responses use the envelope `{ success, data, error, meta }`. Rate limit per key and minute (headers `X-RateLimit-*`)."},"servers":[{"url":"/api/v1"}],"paths":{"/guild":{"get":{"summary":"Server of the key","security":[{"apiKey":[]}],"operationId":"getGuild","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Guild"},"error":{"type":"null"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Server or module not found (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/modules":{"get":{"summary":"Modules with status","description":"Requires the `read:modules` scope.","security":[{"apiKey":[]}],"x-required-scope":"read:modules","operationId":"listModules","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/ModuleSummary"}},"error":{"type":"null"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Server or module not found (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/modules/{moduleId}/schema":{"get":{"summary":"Get module configuration JSON Schema","description":"Requires the `read:config` scope.","security":[{"apiKey":[]}],"x-required-scope":"read:config","operationId":"getModuleSchema","parameters":[{"in":"path","name":"moduleId","schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,30}$","example":"leveling"},"required":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ModuleSchema"},"error":{"type":"null"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Unknown module (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/modules/{moduleId}/config":{"get":{"summary":"Get a module config","description":"Requires the `read:config` scope.","security":[{"apiKey":[]}],"x-required-scope":"read:config","operationId":"getModuleConfig","parameters":[{"in":"path","name":"moduleId","schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,30}$","example":"leveling"},"required":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ModuleConfig"},"error":{"type":"null"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Unknown module (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"put":{"summary":"Replace a module config","description":"Requires the `write:config` scope. Validates against the module's config schema, stores a new revision, writes the audit log (source `api`) and reloads the config in the bot immediately.","security":[{"apiKey":[]}],"x-required-scope":"write:config","operationId":"updateModuleConfig","parameters":[{"in":"path","name":"moduleId","schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,30}$","example":"leveling"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ModuleConfig"},"error":{"type":"null"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"400":{"description":"Invalid JSON, invalid body or config does not match the schema (invalid_json, validation_error, invalid_config)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope) key issuer/reference authorization failed (forbidden), or enabling a Premium module on a free server (premium_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Unknown module (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"expectedRevision is outdated (conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Body larger than 64 KiB (payload_too_large)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/leveling/leaderboard":{"get":{"summary":"Leveling leaderboard","description":"Requires the `read:leveling` scope.","security":[{"apiKey":[]}],"x-required-scope":"read:leveling","operationId":"getLeaderboard","parameters":[{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"in":"query","name":"limit","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardEntry"}},"error":{"type":"null"},"meta":{"$ref":"#/components/schemas/PageMeta"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"400":{"description":"Invalid query (validation_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Server or module not found (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/moderation/cases":{"get":{"summary":"Moderation cases, newest first","description":"Requires the `read:moderation` scope.","security":[{"apiKey":[]}],"x-required-scope":"read:moderation","operationId":"getModerationCases","parameters":[{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"in":"query","name":"limit","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"in":"query","name":"targetId","schema":{"type":"string","pattern":"^\\d{17,20}$"}},{"in":"query","name":"action","schema":{"type":"string","enum":["warn","timeout","untimeout","kick","ban","unban"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/ModerationCase"}},"error":{"type":"null"},"meta":{"$ref":"#/components/schemas/PageMeta"}},"required":["success","data","error","meta"],"additionalProperties":false}}}},"400":{"description":"Invalid query (validation_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (unauthorized, invalid_key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Missing scope (insufficient_scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Server or module not found (not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded – per key, or per IP for requests without a valid key (rate_limited); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal error (internal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"schemas":{"ConfigUpdate":{"type":"object","properties":{"config":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Complete config; validated against the module's config schema"},"enabled":{"type":"boolean"},"expectedRevision":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Optimistic locking: the revision you read (0 for a never-saved config)"}},"required":["config","expectedRevision"],"additionalProperties":false},"Guild":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":["string","null"]},"locale":{"type":"string"},"tier":{"type":"string","enum":["free","premium"]}},"required":["id","name","icon","locale","tier"],"additionalProperties":false},"ErrorEnvelope":{"type":"object","properties":{"success":{"type":"boolean","const":false},"data":{"type":"null"},"error":{"$ref":"#/components/schemas/ApiError"},"meta":{"type":"null"}},"required":["success","data","error","meta"],"additionalProperties":false,"description":"Uniform error response"},"ApiError":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode"},"message":{"type":"string"},"details":{"description":"e.g. validation issues for validation_error/invalid_config"}},"required":["code","message"],"additionalProperties":false},"ApiErrorCode":{"type":"string","enum":["unauthorized","forbidden_origin","forbidden","invalid_key","insufficient_scope","rate_limited","not_found","invalid_json","validation_error","invalid_config","payload_too_large","premium_required","conflict","internal"]},"ModuleSummary":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"tier":{"type":"string","enum":["free","premium"],"description":"Minimum tier to enable the module"},"enabled":{"type":"boolean","description":"Switched on (stored or default)"},"availability":{"type":"string","enum":["enabled","disabled","premium_required"]}},"required":["id","name","tier","enabled","availability"],"additionalProperties":false},"ModuleSchema":{"type":"object","properties":{"moduleId":{"type":"string"},"schemaVersion":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["moduleId","schemaVersion","schema"],"additionalProperties":false,"description":"JSON Schema for config input; runtime validation additionally enforces refinements"},"ModuleConfig":{"type":"object","properties":{"moduleId":{"type":"string"},"enabled":{"type":"boolean"},"revision":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"0 = never saved; pass as expectedRevision"},"schemaVersion":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"config":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Validated; missing fields filled with defaults"},"valid":{"type":"boolean","description":"false = stored config is invalid, defaults are active"}},"required":["moduleId","enabled","revision","schemaVersion","config","valid"],"additionalProperties":false},"LeaderboardEntry":{"type":"object","properties":{"rank":{"type":"integer","minimum":1,"maximum":9007199254740991},"userId":{"type":"string"},"xp":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"level":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"messages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"voiceSeconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["rank","userId","xp","level","messages","voiceSeconds"],"additionalProperties":false},"PageMeta":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":9007199254740991},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"total":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["page","limit","total"],"additionalProperties":false},"ModerationCase":{"type":"object","properties":{"caseNumber":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"action":{"type":"string","enum":["warn","timeout","untimeout","kick","ban","unban"]},"targetId":{"type":"string"},"moderatorId":{"type":"string"},"reason":{"type":["string","null"]},"durationSeconds":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"revokedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]}},"required":["caseNumber","action","targetId","moderatorId","reason","durationSeconds","expiresAt","createdAt","revokedAt"],"additionalProperties":false}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Authorization: Bearer mdly_…"}}}}