{
  "openapi": "3.1.0",
  "info": {
    "title": "Zipir API",
    "version": "1.0.0",
    "summary": "Documents in, narrated lessons out.",
    "description": "Create a lesson from a document, a block of text or a public page, then follow it and download the video.\n\nA lesson made here goes through the same pipeline as the panel: the document is read, a storyboard is prepared, and **no frame is produced until the owner approves the storyboard in the panel** (`needs_approval` in GET /v1/lessons/{id} says when).\n\nEvery answer carries `x-request-id`, and every error is `{error:{code,message,request_id}}`.\n\nQuota is 402, request rate is 429 (with `Retry-After`)."
  },
  "servers": [
    {
      "url": "https://api.zipirapp.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "formats"
    },
    {
      "name": "lessons"
    },
    {
      "name": "usage"
    }
  ],
  "paths": {
    "/v1/formats": {
      "get": {
        "operationId": "listFormats",
        "tags": [
          "formats"
        ],
        "summary": "The video formats a lesson can use",
        "description": "The same catalog the panel and the upload endpoint accept; read it instead of hard-coding ids.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Every format that is ready today.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatList"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while listing formats.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/lessons": {
      "post": {
        "operationId": "createLesson",
        "tags": [
          "lessons"
        ],
        "summary": "Start a lesson (202)",
        "description": "Send a document as multipart, or `text`/`url` as JSON. The lesson id is usable immediately; `GET /v1/lessons/{id}` follows it. With an `Idempotency-Key`, the same request inside 24 hours returns the same lesson (the same key with a different body is 409).",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9._:-]+$"
            },
            "description": "Any unique value; reuse it to retry safely."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonJson"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonFile"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: the lesson is queued.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonCreated"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, empty text or unknown format.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Quota: the key's monthly minutes or the account's own limit is used up.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was used with a different body, or its first request is still running.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The document (50 MB) or the text (5 MB) is too large.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "The file is not a document this pipeline can read.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The page or document had no readable text.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while creating a lesson.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The URL could not be fetched.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/lessons/{id}": {
      "get": {
        "operationId": "getLesson",
        "tags": [
          "lessons"
        ],
        "summary": "One lesson: status, phase, duration, error",
        "description": "`status` is the machine state (queued | rendering | done | failed); `phase` is finer. `needs_approval` is true while the storyboard waits for its owner in the panel.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The lesson.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "lesson"
                  ],
                  "properties": {
                    "lesson": {
                      "$ref": "#/components/schemas/Lesson"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No lesson with this id belongs to the key's account.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while reading a lesson.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteLesson",
        "tags": [
          "lessons"
        ],
        "summary": "Stop and delete a lesson",
        "description": "Cancels work in flight, removes its queue jobs and deletes its files and rows. Idempotent from the caller's side: a missing lesson is 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What was deleted.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No lesson with this id belongs to the key's account.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while deleting a lesson.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/lessons/{id}/download": {
      "get": {
        "operationId": "getDownload",
        "tags": [
          "lessons"
        ],
        "summary": "A signed, time-limited download link",
        "description": "The answer is a URL (900 s) to the finished MP4, not the file itself. `variant=720` is the light copy every plan gets; `variant=1080` is the full quality and needs a paid plan (402 `plan_required` on the free one).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "variant",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "720",
                "1080"
              ],
              "default": "1080"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The signed link.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Download"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "1080p on the free plan (`plan_required`).",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No lesson with this id belongs to the key's account.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not rendered yet (`not_ready`), or its file passed its retention window (`media_expired`).",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while signing a download.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "tags": [
          "usage"
        ],
        "summary": "This key's minutes for the account's current period",
        "description": "The limit is the account's existing entitlement (optionally narrowed per key at creation), never a second rule; `used` counts finished video minutes of lessons this key started.",
        "responses": {
          "200": {
            "description": "Minutes and rate of this key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or unknown API key.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The key's per-minute request limit is full; Retry-After says when to come back.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "retry-after": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error while reading usage.",
            "headers": {
              "x-request-id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key from the panel (Hesap → API anahtarları): `Authorization: Bearer zipir_...`."
      }
    },
    "headers": {
      "RequestId": {
        "description": "The correlation id of this request; the same value is in an error body.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds until the request would be accepted again.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable: unauthorized, rate_limited, quota_exceeded, plan_required, lesson_not_found, not_ready, media_expired, idempotency_key_reused, idempotency_in_progress, invalid_format, invalid_body, too_large, fetch_failed, no_text, daily_jobs, active_jobs, user_budget, trial_plans, trial_seconds, free_full"
              },
              "message": {
                "type": "string",
                "description": "Human-readable Turkish sentence, safe to show."
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "Format": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "briefing",
              "clay",
              "docwalk",
              "whiteboard",
              "illustrated",
              "intuition",
              "panels",
              "brand",
              "mascothost",
              "timeline",
              "cutaway",
              "mapstory"
            ]
          },
          "name": {
            "type": "string",
            "description": "Turkish display name."
          },
          "description": {
            "type": "string",
            "description": "One Turkish sentence: what this format does."
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "library"
            ]
          }
        }
      },
      "FormatList": {
        "type": "object",
        "required": [
          "formats"
        ],
        "properties": {
          "formats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Format"
            }
          }
        }
      },
      "CreateLessonJson": {
        "type": "object",
        "description": "A lesson source: `text` or `url`, not both required — one of them must be present.",
        "properties": {
          "text": {
            "type": "string",
            "description": "The lesson text itself (UTF-8, max 5 MB).",
            "maxLength": 5242880
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "A public http(s) page; its readable text becomes the document."
          },
          "format": {
            "type": "string",
            "enum": [
              "briefing",
              "clay",
              "docwalk",
              "whiteboard",
              "illustrated",
              "intuition",
              "panels",
              "brand",
              "mascothost",
              "timeline",
              "cutaway",
              "mapstory"
            ],
            "description": "Video format. GET /v1/formats lists the live set."
          },
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "Shown in the panel and used in the download file name."
          }
        },
        "anyOf": [
          {
            "required": [
              "text"
            ]
          },
          {
            "required": [
              "url"
            ]
          }
        ]
      },
      "CreateLessonFile": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "PDF, DOCX, PPTX, MD or TXT (max 50 MB)."
          },
          "format": {
            "type": "string",
            "enum": [
              "briefing",
              "clay",
              "docwalk",
              "whiteboard",
              "illustrated",
              "intuition",
              "panels",
              "brand",
              "mascothost",
              "timeline",
              "cutaway",
              "mapstory"
            ],
            "description": "Video format. GET /v1/formats lists the live set."
          },
          "title": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "LessonCreated": {
        "type": "object",
        "required": [
          "lesson"
        ],
        "properties": {
          "lesson": {
            "type": "object",
            "required": [
              "id",
              "status",
              "created_at"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "rendering",
                  "done",
                  "failed"
                ]
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "Lesson": {
        "type": "object",
        "required": [
          "id",
          "title",
          "status",
          "phase",
          "needs_approval"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "rendering",
              "done",
              "failed"
            ]
          },
          "phase": {
            "type": "string",
            "description": "Where the pipeline is: upload, reading, storyboard_ready, approved, rendering, done, failed, cancelled."
          },
          "format": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "briefing",
              "clay",
              "docwalk",
              "whiteboard",
              "illustrated",
              "intuition",
              "panels",
              "brand",
              "mascothost",
              "timeline",
              "cutaway",
              "mapstory",
              null
            ]
          },
          "duration_sec": {
            "type": [
              "number",
              "null"
            ],
            "description": "Finished video length; null until a render is done."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "needs_approval": {
            "type": "boolean",
            "description": "True while the storyboard waits for its owner: no video is rendered until they approve it in the panel."
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "code",
                  "message"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            ]
          }
        }
      },
      "Download": {
        "type": "object",
        "required": [
          "url",
          "expires_in",
          "variant"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed link to the MP4."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds the link stays valid (900)."
          },
          "duration_sec": {
            "type": [
              "number",
              "null"
            ]
          },
          "variant": {
            "type": "string",
            "enum": [
              "720",
              "1080"
            ]
          }
        }
      },
      "Deleted": {
        "type": "object",
        "required": [
          "deleted",
          "objects"
        ],
        "properties": {
          "deleted": {
            "type": "boolean"
          },
          "objects": {
            "type": "integer",
            "description": "Stored files removed."
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": [
          "key",
          "period",
          "minutes",
          "rate_per_minute"
        ],
        "properties": {
          "key": {
            "type": "object",
            "required": [
              "id",
              "name",
              "prefix"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string",
                "description": "The first 8 secret characters; the key itself is only ever shown once, at creation."
              }
            }
          },
          "period": {
            "type": "object",
            "required": [
              "start",
              "end"
            ],
            "properties": {
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "end": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "minutes": {
            "type": "object",
            "required": [
              "limit",
              "used",
              "remaining",
              "over"
            ],
            "properties": {
              "limit": {
                "type": "number"
              },
              "used": {
                "type": "number"
              },
              "remaining": {
                "type": "number"
              },
              "over": {
                "type": "boolean"
              }
            }
          },
          "rate_per_minute": {
            "type": "integer"
          }
        }
      }
    }
  }
}
