567 lines
14 KiB
JSON
567 lines
14 KiB
JSON
{
|
|
"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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|