openapi: 3.0.3
info:
  title: Adflow API
  description: |
    Adflow is an HTML ad builder with AI-powered image generation, design systems, and multi-format export.

    ## Authentication
    All endpoints require an API key via the `X-API-Key` header. Create keys at adflow.design or via `POST /api/auth/api-keys`.

    ## Response format
    All JSON responses use `{ ok: true, ...data }` on success or `{ ok: false, error: "message" }` on failure.
  version: 1.0.0
  contact:
    name: Popcandi
    url: https://adflow.design

servers:
  - url: https://adflow.design
    description: Production

security:
  - apiKey: []

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key with `adk_` prefix. Create via POST /api/auth/api-keys.

paths:
  # ── Auth ──────────────────────────────────────────────
  /api/auth/me:
    get:
      tags: [Auth]
      summary: Get current user profile
      responses:
        '200':
          description: User profile
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  email: { type: string }
                  displayName: { type: string }
                  isAdmin: { type: boolean }

  /api/auth/api-keys:
    get:
      tags: [Auth]
      summary: List API keys
      responses:
        '200':
          description: List of keys (raw key values are not returned)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  keys:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        name: { type: string }
                        last_used_at: { type: string, format: date-time }
                        created_at: { type: string, format: date-time }
    post:
      tags: [Auth]
      summary: Create a new API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, description: "Human-readable label" }
      responses:
        '200':
          description: Key created (raw key only shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  key: { type: string, description: "Raw API key — save this, it won't be shown again" }
                  name: { type: string }
    delete:
      tags: [Auth]
      summary: Revoke an API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        '200':
          description: Key revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }

  # ── Design Systems ────────────────────────────────────
  /api/design-systems/list:
    get:
      tags: [Design Systems]
      summary: List accessible design systems
      responses:
        '200':
          description: Design systems the authenticated user can access
          content:
            application/json:
              schema:
                type: object
                properties:
                  designSystems:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        name: { type: string }
                        data: { type: object, description: "Brand colours, fonts, logos, etc." }

  /api/design-systems/create:
    post:
      tags: [Design Systems]
      summary: Create a design system
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, data]
              properties:
                name: { type: string }
                data:
                  type: object
                  description: "Brand data — colours, fonts, logos, etc."
      responses:
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string, description: "Auto-generated slug from name" }
                  name: { type: string }
        '409':
          description: Design system with that ID already exists

  /api/design-systems/save:
    post:
      tags: [Design Systems]
      summary: Update a design system
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id, data]
              properties:
                id: { type: string }
                data: { type: object }
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string }

  /api/design-systems/delete:
    post:
      tags: [Design Systems]
      summary: Delete a design system
      description: Cannot delete the "default" design system.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string }

  # ── Projects ──────────────────────────────────────────
  /api/projects/list:
    get:
      tags: [Projects]
      summary: List projects
      parameters:
        - name: designSystemId
          in: query
          schema: { type: string }
          description: Filter by design system ID
      responses:
        '200':
          description: Project list
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  projects:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        filename: { type: string }
                        name: { type: string }
                        designSystemId: { type: string }
                        sceneCount: { type: integer }
                        savedAt: { type: string, format: date-time }

  /api/projects/{id}:
    get:
      tags: [Projects]
      summary: Get a project by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Full project data
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string }
                  filename: { type: string }
                  project: { type: object, description: "Complete project JSON" }
        '404':
          description: Project not found

  /api/projects/save:
    post:
      tags: [Projects]
      summary: Create or update a project
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, project]
              properties:
                filename: { type: string }
                project: { type: object, description: "Full project data" }
                projectId: { type: string, description: "If updating existing project" }
      responses:
        '200':
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string }
                  filename: { type: string }

  /api/projects/delete:
    post:
      tags: [Projects]
      summary: Delete a project
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }

  /api/projects/duplicate:
    post:
      tags: [Projects]
      summary: Duplicate a project
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        '200':
          description: Duplicated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string, description: "New project ID" }
                  name: { type: string }

  # ── AI Generation ─────────────────────────────────────
  /api/genai/generate:
    post:
      tags: [AI Generation]
      summary: Generate an image from a text prompt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt: { type: string, description: "Text prompt for image generation" }
                model:
                  type: string
                  enum: [flux-dev, flux-pro, gpt-image-1, imagen4, imagen4-fast, nano-banana-pro, ideogram-v3]
                  default: flux-dev
                width: { type: integer, default: 2160 }
                height: { type: integer, default: 2160 }
                filename: { type: string, default: generated.png }
      responses:
        '200':
          description: Image generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  path: { type: string, description: "Vercel Blob URL" }
                  filename: { type: string }
                  cdn_url: { type: string, description: "Upstream CDN URL" }

  /api/genai/outpaint:
    post:
      tags: [AI Generation]
      summary: Extend image edges with AI outpainting
      description: Expand an image in any direction using AI. Send the image as a base64 data URI.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image_data]
              properties:
                image_data: { type: string, description: "Base64 data URI of the source image" }
                expand_top: { type: integer, default: 0, description: "Pixels to expand at top" }
                expand_bottom: { type: integer, default: 0 }
                expand_left: { type: integer, default: 0 }
                expand_right: { type: integer, default: 0 }
                filename: { type: string, default: extended.png }
      responses:
        '200':
          description: Image extended
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  path: { type: string }
                  filename: { type: string }

  /api/genai/enhance:
    post:
      tags: [AI Generation]
      summary: Upscale/enhance an image
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image_url]
              properties:
                image_url: { type: string }
                model:
                  type: string
                  enum: [aura-sr, creative-upscaler, clarity-upscaler]
                  default: aura-sr
                filename: { type: string, default: enhanced.png }
      responses:
        '200':
          description: Image enhanced
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  url: { type: string }
                  filename: { type: string }

  /api/genai/retouch:
    post:
      tags: [AI Generation]
      summary: Retouch regions of an image (erase or inpaint)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image_url, mask_url]
              properties:
                image_url: { type: string }
                mask_url: { type: string, description: "Black/white mask — white areas get retouched" }
                mode: { type: string, enum: [erase, inpaint], default: erase, description: "Erase removes objects; inpaint replaces with prompt-described content" }
                prompt: { type: string, default: "", description: "Required for inpaint mode, ignored for erase" }
                filename: { type: string, default: retouched.png }
      responses:
        '200':
          description: Image retouched
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  url: { type: string }
                  filename: { type: string }

  /api/genai/crop:
    post:
      tags: [AI Generation]
      summary: Crop an image
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image_url]
              properties:
                image_url: { type: string }
                crop_top: { type: number, default: 0, description: "Percentage to crop from top (0-40)" }
                crop_bottom: { type: number, default: 0 }
                crop_left: { type: number, default: 0 }
                crop_right: { type: number, default: 0 }
                frame_width: { type: integer, description: "Frame width in pixels (for scale-aware crop)" }
                frame_height: { type: integer }
                bg_scale: { type: number, default: 100 }
                focus_x: { type: number, default: 50 }
                focus_y: { type: number, default: 50 }
                filename: { type: string, default: cropped.png }
      responses:
        '200':
          description: Image cropped
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  path: { type: string }
                  filename: { type: string }
                  width: { type: integer }
                  height: { type: integer }

  /api/genai/vector:
    post:
      tags: [AI Generation]
      summary: Generate an SVG illustration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt: { type: string }
                model: { type: string, default: arrow-1.1 }
                filename: { type: string, default: generated.svg }
      responses:
        '200':
          description: SVG generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  svg: { type: string, description: "Raw SVG content" }
                  path: { type: string }
                  filename: { type: string }

  /api/genai/list:
    get:
      tags: [AI Generation]
      summary: List generated images
      responses:
        '200':
          description: Generated images
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  images:
                    type: array
                    items:
                      type: object
                      properties:
                        url: { type: string }
                        filename: { type: string }
                        uploadedAt: { type: string, format: date-time }
                        size: { type: integer }

  /api/genai/save-image:
    post:
      tags: [AI Generation]
      summary: Save an image URL to storage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string }
                filename: { type: string, default: generated.png }
      responses:
        '200':
          description: Image saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  path: { type: string }
                  filename: { type: string }

  # ── Renders ───────────────────────────────────────────
  /api/renders/figma-html:
    post:
      tags: [Renders]
      summary: Export project scenes as HTML for Figma
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId]
              properties:
                projectId: { type: string }
                format:
                  type: string
                  enum: [square, portrait, vertical, landscape, wide]
      responses:
        '200':
          description: HTML scenes
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  projectId: { type: string }
                  projectName: { type: string }
                  format: { type: string }
                  width: { type: integer }
                  height: { type: integer }
                  scenes:
                    type: array
                    items:
                      type: object
                      properties:
                        index: { type: integer }
                        headline: { type: string }
                        width: { type: integer }
                        height: { type: integer }
                        html: { type: string }

  /api/renders/screenshot:
    post:
      tags: [Renders]
      summary: Render HTML to a PNG screenshot
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [html, width, height]
              properties:
                html: { type: string, description: "Complete HTML document" }
                width: { type: integer, maximum: 4096 }
                height: { type: integer, maximum: 4096 }
      responses:
        '200':
          description: PNG image binary
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  /api/renders/save:
    post:
      tags: [Renders]
      summary: Save a rendered image
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dataUri]
              properties:
                dataUri: { type: string, description: "Base64 data URI" }
                filename: { type: string, default: render.png }
      responses:
        '200':
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  filename: { type: string }
                  path: { type: string }

  # ── Uploads ───────────────────────────────────────────
  /api/uploads/image:
    post:
      tags: [Uploads]
      summary: Upload an image file
      description: Send raw binary image data (not JSON). Set Content-Type to the image MIME type.
      parameters:
        - name: filename
          in: query
          schema: { type: string }
      requestBody:
        required: true
        content:
          image/png:
            schema: { type: string, format: binary }
          image/jpeg:
            schema: { type: string, format: binary }
          image/webp:
            schema: { type: string, format: binary }
      responses:
        '200':
          description: Uploaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  url: { type: string }
