{
    "openapi": "3.1.0",
    "info": {
        "title": "Cartoonicate Partner API",
        "version": "1.0.0",
        "description": "Generate AI cartoons programmatically.\n\n**Authentication:** send your secret API key as `Authorization: Bearer <key>` or `X-API-Key: <key>`. Keys are issued in the developer panel. Calls are server-to-server only — never expose a key in a browser or mobile app.\n\n**Async model:** creating a cartoon returns immediately (202) with render ids. Poll `GET /cartoons.php?id=` or configure a webhook to receive `cartoon.completed`. Preview images are always watermarked.\n\n**Limits:** each plan has a monthly preview quota and a per-minute rate limit (`429` with `Retry-After` when exceeded; `402` when the monthly quota is exhausted).",
        "contact": {
            "name": "Cartoonicate",
            "url": "https://cartoonicate.com"
        }
    },
    "servers": [
        {
            "url": "https://cartoonicate.com/api/v1/partner"
        }
    ],
    "security": [
        {
            "ApiKeyHeader": []
        },
        {
            "BearerAuth": []
        }
    ],
    "paths": {
        "/ping.php": {
            "get": {
                "summary": "Health / key check",
                "operationId": "ping",
                "responses": {
                    "200": {
                        "description": "Key is valid",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PingResponse"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/cartoons.php": {
            "post": {
                "summary": "Create a cartoon (async)",
                "operationId": "createCartoon",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateCartoonRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Queued",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CreateCartoonResponse"
                                }
                            }
                        }
                    },
                    "402": {
                        "$ref": "#/components/responses/QuotaExceeded"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            },
            "get": {
                "summary": "List cartoons, or fetch one with ?id=",
                "operationId": "listOrGetCartoons",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Fetch a single cartoon (with renders) instead of listing."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 0
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A cartoon or a list",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/animations.php": {
            "post": {
                "summary": "Animate a render into a short clip (async, premium)",
                "description": "Animate one of your 'ready' renders into a short watermarked clip. Premium: requires a plan with `allow_video` enabled (402 otherwise). Async: returns 202 with an animation id; poll `GET /animations.php?id=` for the watermarked preview. The un-watermarked final clip is never served over the API.",
                "operationId": "createAnimation",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateAnimationRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Queued",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CreateAnimationResponse"
                                }
                            }
                        }
                    },
                    "402": {
                        "$ref": "#/components/responses/QuotaExceeded"
                    },
                    "409": {
                        "description": "The render is not ready to animate",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    }
                }
            },
            "get": {
                "summary": "Get an animation, or list your animations",
                "operationId": "getAnimations",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Fetch a single animation instead of listing."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 0
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "An animation or a list",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/account.php": {
            "get": {
                "summary": "Account, plan, and quota usage",
                "operationId": "getAccount",
                "responses": {
                    "200": {
                        "description": "Account",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/usage.php": {
            "get": {
                "summary": "Recent API calls + month-to-date usage",
                "operationId": "getUsage",
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 0
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Usage",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        }
    },
    "webhooks": {
        "cartoon.completed": {
            "post": {
                "summary": "Sent to your webhook URL when a cartoon finishes rendering.",
                "description": "Signed with HMAC-SHA256 over `<timestamp>.<body>` using your account signing secret, in the header `X-Cartoonicate-Signature: t=<ts>,v1=<hex>`. Recompute and compare (timing-safe), and reject stale timestamps.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WebhookEvent"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledge with any 2xx."
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "ApiKeyHeader": {
                "type": "apiKey",
                "in": "header",
                "name": "X-API-Key"
            },
            "BearerAuth": {
                "type": "http",
                "scheme": "bearer"
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Missing or invalid API key",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "NotFound": {
                "description": "Resource not found",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "ValidationError": {
                "description": "Invalid input",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "QuotaExceeded": {
                "description": "Monthly quota exhausted",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "RateLimited": {
                "description": "Too many requests (see Retry-After)",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean",
                        "example": false
                    },
                    "message": {
                        "type": "string"
                    },
                    "data": {
                        "type": "null"
                    }
                }
            },
            "CreateCartoonRequest": {
                "type": "object",
                "required": [
                    "subject",
                    "problem",
                    "location"
                ],
                "properties": {
                    "subject": {
                        "type": "string",
                        "description": "The subject(s)' appearance.",
                        "maxLength": 1000
                    },
                    "problem": {
                        "type": "string",
                        "description": "The problem / pet-peeve / point.",
                        "maxLength": 1000
                    },
                    "location": {
                        "type": "string",
                        "description": "The room / location.",
                        "maxLength": 1000
                    },
                    "holiday": {
                        "type": "string",
                        "enum": [
                            "birthday",
                            "christmas",
                            "halloween",
                            "new_year",
                            "valentines",
                            "thanksgiving",
                            "st_patricks",
                            "fourth_of_july"
                        ],
                        "nullable": true
                    },
                    "style_preset": {
                        "type": "string",
                        "enum": [
                            "new_yorker",
                            "anime",
                            "pixar_3d",
                            "newspaper_comic",
                            "sticker"
                        ],
                        "nullable": true
                    },
                    "tone": {
                        "type": "string",
                        "enum": [
                            "playful",
                            "medium",
                            "savage"
                        ],
                        "nullable": true
                    },
                    "aspect_ratio": {
                        "type": "string",
                        "enum": [
                            "1:1",
                            "9:16",
                            "16:9"
                        ],
                        "nullable": true
                    },
                    "format": {
                        "type": "string",
                        "enum": [
                            "single",
                            "gag",
                            "reaction",
                            "comic",
                            "sticker_sheet"
                        ],
                        "default": "single",
                        "description": "Output layout. single = one framed cartoon; gag = single panel + caption; reaction = top/bottom caption meme; comic = multi-panel strip; sticker_sheet = grid of expressions."
                    },
                    "panel_count": {
                        "type": "integer",
                        "enum": [
                            3,
                            4,
                            6,
                            8
                        ],
                        "nullable": true,
                        "description": "Panels for multi-panel formats (comic / sticker_sheet). Ignored for single-image formats."
                    },
                    "title_banner": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 80,
                        "description": "Optional top banner / caption rendered verbatim (text formats only)."
                    },
                    "punchline_banner": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 80,
                        "description": "Optional bottom banner / caption rendered verbatim (text formats only)."
                    },
                    "variants": {
                        "type": "integer",
                        "description": "How many previews to generate (capped by your plan).",
                        "minimum": 1
                    },
                    "metadata": {
                        "type": "object",
                        "description": "Opaque passthrough echoed back on reads.",
                        "additionalProperties": true
                    }
                }
            },
            "CreateCartoonResponse": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean",
                        "example": true
                    },
                    "data": {
                        "type": "object",
                        "properties": {
                            "cartoon": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    },
                                    "status": {
                                        "type": "string",
                                        "example": "processing"
                                    },
                                    "variants": {
                                        "type": "integer"
                                    },
                                    "render_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "CreateAnimationRequest": {
                "type": "object",
                "required": [
                    "render_id"
                ],
                "properties": {
                    "render_id": {
                        "type": "integer",
                        "description": "A 'ready' render (from one of your cartoons) to animate."
                    }
                }
            },
            "CreateAnimationResponse": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean",
                        "example": true
                    },
                    "data": {
                        "type": "object",
                        "properties": {
                            "animation": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    },
                                    "render_id": {
                                        "type": "integer"
                                    },
                                    "cartoon_id": {
                                        "type": "integer"
                                    },
                                    "status": {
                                        "type": "string",
                                        "example": "processing"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "PingResponse": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean"
                    },
                    "data": {
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "example": "ok"
                            },
                            "account": {
                                "type": "object"
                            }
                        }
                    }
                }
            },
            "WebhookEvent": {
                "type": "object",
                "properties": {
                    "event": {
                        "type": "string",
                        "example": "cartoon.completed"
                    },
                    "created": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "data": {
                        "type": "object",
                        "properties": {
                            "cartoon_id": {
                                "type": "integer"
                            },
                            "renders": {
                                "type": "array",
                                "items": {
                                    "type": "object"
                                }
                            },
                            "metadata": {
                                "type": "object",
                                "nullable": true
                            }
                        }
                    }
                }
            }
        }
    }
}