{
  "openapi": "3.1.0",
  "info": {
    "title": "FlipLetters API",
    "version": "1.0.0",
    "summary": "Cloud render, teams, workspaces, and API keys for FlipLetters.",
    "description": "FlipLetters makes split-flap / flip-dot board videos.\n\nThis specification documents the **real** public HTTP surface.\nIt is small on purpose.\n\n- **Drafting boards needs no REST API.** Use flipcode (npm `@flipletters/flipcode`)\n  or MCP: hosted Streamable HTTP at https://app.flipletters.com/api/mcp\n  (alias /mcp/v1) or local stdio `npx -y flipletters-mcp-server`.\n- **HTTP APIs run on https://app.flipletters.com.** flipletters.com publishes\n  this spec, docs, and agent files; it does not execute render/team calls.\n- **Auth:** `Authorization: Bearer <firebase-id-token>` (Studio session) or\n  `Authorization: Bearer flk_…` (key minted by a human in Studio → Account →\n  API keys & agents). Scopes: `render`, `team`, `postiz`.\n- **No public webhooks** are documented. Stripe / TikTok / billing hooks are\n  internal Studio integrations, not a partner webhook API.\n- Other `/api/*` routes exist for the Studio UI (analytics, catalog checkout,\n  admin, cron). They are session-only and are omitted here.",
    "contact": {
      "name": "FlipLetters",
      "email": "dev@flipletters.com",
      "url": "https://flipletters.com/docs/api"
    },
    "license": {
      "name": "Proprietary Studio API; flipcode parser is MIT",
      "url": "https://www.npmjs.com/package/@flipletters/flipcode"
    }
  },
  "servers": [
    {
      "url": "https://app.flipletters.com",
      "description": "FlipLetters Studio (live HTTP API)"
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "Spec and docs on flipletters.com"
    },
    {
      "name": "Render",
      "description": "Cloud MP4 export (scope: render)"
    },
    {
      "name": "Teams",
      "description": "Shared template library (scope: team)"
    },
    {
      "name": "Keys",
      "description": "Mint/revoke flk_ keys (Firebase session only)"
    },
    {
      "name": "Workspaces",
      "description": "Agency projects and Postiz destinations"
    },
    {
      "name": "Postiz",
      "description": "Schedule/draft/post via a connected Postiz"
    }
  ],
  "paths": {
    "/openapi.json": {
      "servers": [
        {
          "url": "https://flipletters.com"
        }
      ],
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "This OpenAPI document (published on flipletters.com)",
        "operationId": "getOpenApiJson",
        "responses": {
          "200": {
            "description": "OpenAPI 3 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/render": {
      "get": {
        "tags": [
          "Render"
        ],
        "summary": "Render capability probe, or job status when jobId is set",
        "operationId": "getRender",
        "parameters": [
          {
            "name": "jobId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "When set, return that job's status. Requires render auth. Without jobId, returns { enabled, mode } with no auth."
          }
        ],
        "responses": {
          "200": {
            "description": "Capability { enabled, mode: local|cloud } or job { jobId, status, progress, url, fileName, error }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "post": {
        "tags": [
          "Render"
        ],
        "summary": "Start a cloud MP4 render under the caller's plan",
        "operationId": "postRender",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "project"
                ],
                "properties": {
                  "project": {
                    "type": "object",
                    "description": "Studio project JSON (compiled from flipcode or the editor). Must include a non-empty slides array.",
                    "additionalProperties": true
                  },
                  "repeat": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 120,
                    "description": "Loop count for ambience-style exports"
                  },
                  "workspaceId": {
                    "type": "string",
                    "description": "Optional project id; required to match a workspace-scoped key"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ jobId, statusUrl }"
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "402": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          },
          "502": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "delete": {
        "tags": [
          "Render"
        ],
        "summary": "Cancel a queued or in-flight cloud render",
        "operationId": "deleteRender",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok, status: cancelled }"
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "409": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/team": {
      "post": {
        "tags": [
          "Teams"
        ],
        "summary": "Team library actions (create, join, list, share, …)",
        "operationId": "postTeam",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "One POST endpoint; the JSON `action` field selects the operation. Creating a team is a Premium perk; joining by invite is free.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "listTeams",
                      "create",
                      "join",
                      "listTemplates",
                      "leave",
                      "share",
                      "removeTemplate"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string",
                    "description": "Invite code for join"
                  },
                  "teamId": {
                    "type": "string"
                  },
                  "project": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Required for action=share"
                  },
                  "templateId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Action-specific JSON (teams, teamId, templates, …)"
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "402": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "500": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/keys": {
      "get": {
        "tags": [
          "Keys"
        ],
        "summary": "List the signed-in user's API keys (metadata only)",
        "operationId": "listKeys",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "description": "Firebase ID token only. Agents cannot mint keys. The plaintext secret is never listed.",
        "responses": {
          "200": {
            "description": "{ keys: ApiKeyPublic[] }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "post": {
        "tags": [
          "Keys"
        ],
        "summary": "Create an API key (plaintext shown once)",
        "operationId": "createKey",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "workspaceId": {
                    "type": "string",
                    "description": "Optional: scope the key to one project"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ key, plaintext } — store plaintext immediately"
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "delete": {
        "tags": [
          "Keys"
        ],
        "summary": "Revoke a key",
        "operationId": "revokeKey",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "keyId"
                ],
                "properties": {
                  "keyId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok: true }"
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/workspaces": {
      "get": {
        "tags": [
          "Workspaces"
        ],
        "summary": "List projects (ensures a Personal default)",
        "operationId": "listWorkspaces",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ workspaces, activeWorkspaceId, keyWorkspaceId }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "post": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Create a project or set the active one (Studio session only)",
        "operationId": "createOrSwitchWorkspace",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "activeWorkspaceId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ workspace } or { ok, activeWorkspaceId }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/workspaces/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Get one project and its (masked) share destinations",
        "operationId": "getWorkspace",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ workspaceId, destinations }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "patch": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Rename a project (Studio session)",
        "operationId": "renameWorkspace",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Updated workspace"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "delete": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Delete a project (Studio session)",
        "operationId": "deleteWorkspace",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok: true }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/workspaces/{id}/destinations": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Workspaces"
        ],
        "summary": "List Postiz share accounts on a project",
        "operationId": "listDestinations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ destinations }"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      },
      "post": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Add a Postiz destination",
        "operationId": "addDestination",
        "security": [
          {
            "firebaseSession": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "postiz"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "baseUrl": {
                    "type": "string"
                  },
                  "apiKey": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created destination (secret masked)"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/postiz/integrations": {
      "get": {
        "tags": [
          "Postiz"
        ],
        "summary": "List channels on a Postiz destination",
        "operationId": "listPostizIntegrations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspaceId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "destinationId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Postiz integrations list"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          },
          "403": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/postiz/schedule": {
      "post": {
        "tags": [
          "Postiz"
        ],
        "summary": "Upload a FlipLetters export to Postiz and schedule/draft/post",
        "operationId": "schedulePostiz",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspaceId": {
                    "type": "string"
                  },
                  "destinationId": {
                    "type": "string"
                  },
                  "integrationId": {
                    "type": "string"
                  },
                  "date": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "schedule",
                      "draft",
                      "now"
                    ]
                  },
                  "content": {
                    "type": "string"
                  },
                  "fileName": {
                    "type": "string"
                  },
                  "mediaUrl": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Postiz create-post result"
          },
          "401": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Firebase ID token or Studio API key `flk_…`. Keys are created by a signed-in human; they are never self-issued by agents."
      },
      "firebaseSession": {
        "type": "http",
        "scheme": "bearer",
        "description": "Firebase ID token only (browser Studio session). API keys are rejected."
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": [
          "error",
          "code",
          "hint"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message (string, for existing Studio UI)"
          },
          "code": {
            "type": "string",
            "description": "Stable machine code, e.g. NOT_FOUND"
          },
          "hint": {
            "type": "string",
            "description": "Resolution hint for agents and humans"
          }
        }
      }
    },
    "responses": {
      "ApiError": {
        "description": "Structured FlipLetters API error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    }
  }
}
