{ "openapi": "3.1.0", "info": { "title": "The Meme Protocol API", "version": "1.0.0", "description": "Public API for listing approved memes and submitting PNG/JPEG meme uploads. Uploads follow the same moderation, image validation, normalization, and IP quota rules as the browser form." }, "servers": [ { "url": "/" } ], "tags": [ { "name": "memes", "description": "Approved meme discovery and submission" }, { "name": "discovery", "description": "Machine-readable API metadata" } ], "paths": { "/api/memes": { "get": { "tags": [ "memes" ], "summary": "List approved memes", "description": "Returns approved public memes only. Results are paginated and read-limited per IP to reduce abuse.", "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "pageSize", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 48, "default": 12 } } ], "responses": { "200": { "description": "Approved meme page", "headers": { "RateLimit-Limit": { "schema": { "type": "integer" } }, "RateLimit-Remaining": { "schema": { "type": "integer" } }, "RateLimit-Reset": { "schema": { "type": "string", "format": "date-time" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemeListResponse" } } } }, "429": { "$ref": "#/components/responses/RateLimited" } } }, "post": { "tags": [ "memes" ], "summary": "Submit a meme", "description": "Submits one PNG or JPEG image using the same path as the browser upload form. The request must be multipart/form-data with a file field named meme. The server enforces the same size, dimension, pixel, moderation, normalization, and per-IP upload limits as human submissions.", "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": [ "meme" ], "properties": { "meme": { "type": "string", "format": "binary", "description": "PNG or JPEG image, maximum 5 MB, maximum edge 6000 px, maximum 20 megapixels." } } }, "encoding": { "meme": { "contentType": "image/png, image/jpeg" } } } } }, "responses": { "201": { "description": "Upload approved and published immediately", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemeSubmitResponse" } } } }, "202": { "description": "Upload accepted and queued for admin review", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemeSubmitResponse" } } } }, "400": { "$ref": "#/components/responses/Error" }, "411": { "$ref": "#/components/responses/Error" }, "413": { "$ref": "#/components/responses/Error" }, "415": { "$ref": "#/components/responses/Error" }, "422": { "description": "Upload rejected by moderation", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModerationRejected" } } } }, "429": { "$ref": "#/components/responses/Error" } } } }, "/api/memes/{id}": { "get": { "tags": [ "memes" ], "summary": "Get one approved meme", "description": "Returns public metadata for an approved meme. Pending, rejected, deleted, or unknown IDs return 404.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-f0-9]{64}$" } } ], "responses": { "200": { "description": "Approved meme metadata", "headers": { "RateLimit-Limit": { "schema": { "type": "integer" } }, "RateLimit-Remaining": { "schema": { "type": "integer" } }, "RateLimit-Reset": { "schema": { "type": "string", "format": "date-time" } } }, "content": { "application/json": { "schema": { "type": "object", "required": [ "meme", "rateLimit" ], "properties": { "meme": { "$ref": "#/components/schemas/Meme" }, "rateLimit": { "$ref": "#/components/schemas/RateLimit" } } } } } }, "404": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/media/{id}": { "get": { "tags": [ "memes" ], "summary": "Fetch approved meme image", "description": "Returns the normalized WebP image for an approved meme. Media reads are rate-limited per IP.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-f0-9]{64}$" } } ], "responses": { "200": { "description": "WebP meme image", "content": { "image/webp": { "schema": { "type": "string", "format": "binary" } } } }, "404": { "description": "Not found" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/openapi.json": { "get": { "tags": [ "discovery" ], "summary": "OpenAPI document", "responses": { "200": { "description": "OpenAPI 3.1 document", "content": { "application/json": { "schema": { "type": "object" } } } } } } }, "/meme-api.skill.md": { "get": { "tags": [ "discovery" ], "summary": "LLM API consumption instructions", "responses": { "200": { "description": "Markdown skill instructions for LLM clients", "content": { "text/markdown": { "schema": { "type": "string" } } } } } } } }, "components": { "schemas": { "Meme": { "type": "object", "required": [ "id", "createdAt", "byteSize", "width", "height", "mime", "originalMime", "status", "moderationScore", "moderationReason", "viewCount", "downloadCount", "originalName", "url", "downloadUrl", "demo" ], "properties": { "id": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "createdAt": { "type": "string", "format": "date-time" }, "byteSize": { "type": "integer", "minimum": 0 }, "width": { "type": "integer", "minimum": 1 }, "height": { "type": "integer", "minimum": 1 }, "mime": { "type": "string", "const": "image/webp" }, "originalMime": { "type": "string", "enum": [ "image/png", "image/jpeg" ] }, "status": { "type": "string", "enum": [ "approved", "pending" ] }, "moderationScore": { "type": "integer", "minimum": 0, "maximum": 100 }, "moderationReason": { "type": "string" }, "viewCount": { "type": "integer", "minimum": 0 }, "downloadCount": { "type": "integer", "minimum": 0 }, "originalName": { "type": "string" }, "url": { "type": "string", "format": "uri-reference" }, "downloadUrl": { "type": "string", "format": "uri-reference" }, "demo": { "type": "boolean" } } }, "MemeListResponse": { "type": "object", "required": [ "page", "pageSize", "total", "totalPages", "memes", "rateLimit" ], "properties": { "page": { "type": "integer" }, "pageSize": { "type": "integer" }, "total": { "type": "integer" }, "totalPages": { "type": "integer" }, "memes": { "type": "array", "items": { "$ref": "#/components/schemas/Meme" } }, "rateLimit": { "$ref": "#/components/schemas/RateLimit" } } }, "MemeSubmitResponse": { "type": "object", "required": [ "meme", "quota", "message" ], "properties": { "meme": { "$ref": "#/components/schemas/Meme" }, "quota": { "$ref": "#/components/schemas/UploadQuota" }, "message": { "type": "string", "enum": [ "Upload approved.", "Upload queued for admin review." ] } } }, "UploadQuota": { "type": "object", "required": [ "remainingHour", "remainingDay", "remainingGlobalDay" ], "properties": { "remainingHour": { "type": "integer", "minimum": 0 }, "remainingDay": { "type": "integer", "minimum": 0 }, "remainingGlobalDay": { "type": "integer", "minimum": 0 } } }, "RateLimit": { "type": "object", "required": [ "limit", "remaining", "resetAt" ], "properties": { "limit": { "type": "integer" }, "remaining": { "type": "integer" }, "resetAt": { "type": "string", "format": "date-time" } } }, "Error": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string" } } }, "ModerationRejected": { "type": "object", "required": [ "error", "moderationScore" ], "properties": { "error": { "type": "string" }, "moderationScore": { "type": "integer", "minimum": 0, "maximum": 100 } } } }, "responses": { "Error": { "description": "Error response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "RateLimited": { "description": "Rate limit exceeded", "headers": { "Retry-After": { "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }