Add Public API

This commit is contained in:
2026-06-26 15:23:53 -03:00
parent 36edd32fa3
commit d80d596664
12 changed files with 891 additions and 42 deletions
+566
View File
@@ -0,0 +1,566 @@
{
"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"
}
}
}
}
}
}
}