openapi: 3.1.0
info:
  title: Excalidraw Full Workspace API
  version: 0.1.0
  description: Backend-owned workspace API for auth, teams, drawings, folders, projects, templates, revisions, and activity.
servers:
  - url: /api
security:
  - cookieSession: []
components:
  securitySchemes:
    cookieSession:
      type: apiKey
      in: cookie
      name: excalidraw_session
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
    User:
      type: object
      required: [id, name, username, email, locale, timezone, created_at, updated_at]
      properties:
        id: { type: string }
        name: { type: string }
        username: { type: string }
        email: { type: string, format: email }
        avatar_url: { type: [string, "null"] }
        locale: { type: string }
        timezone: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Session:
      type: object
      required: [id, user_id, expires_at, created_at]
      properties:
        id: { type: string }
        user_id: { type: string }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    Team:
      type: object
      required: [id, name, slug, owner_user_id, plan_type, created_at, updated_at]
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string }
        owner_user_id: { type: string }
        plan_type: { type: string, enum: [free, pro] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    TeamMembership:
      type: object
      required: [id, team_id, user_id, role, joined_at]
      properties:
        id: { type: string }
        team_id: { type: string }
        user_id: { type: string }
        role: { type: string, enum: [owner, admin, editor, viewer] }
        joined_at: { type: string, format: date-time }
        user:
          $ref: "#/components/schemas/User"
    Drawing:
      type: object
      required: [id, team_id, title, owner_user_id, visibility, is_archived, created_at, updated_at]
      properties:
        id: { type: string }
        team_id: { type: string }
        folder_id: { type: [string, "null"] }
        project_id: { type: [string, "null"] }
        slug: { type: [string, "null"] }
        title: { type: string }
        description: { type: [string, "null"] }
        owner_user_id: { type: string }
        latest_revision_id: { type: [string, "null"] }
        visibility: { type: string, enum: [private, team, restricted, public-link] }
        is_archived: { type: boolean }
        thumbnail_asset_id: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        deleted_at: { type: [string, "null"], format: date-time }
        owner:
          $ref: "#/components/schemas/User"
    DrawingRevision:
      type: object
      required: [id, drawing_id, revision_number, snapshot_path, snapshot_size, content_hash, created_by, created_at]
      properties:
        id: { type: string }
        drawing_id: { type: string }
        revision_number: { type: integer }
        snapshot_path: { type: string }
        snapshot_size: { type: integer, format: int64 }
        content_hash: { type: string }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
        change_summary: { type: [string, "null"] }
        snapshot: {}
    Template:
      type: object
      required: [id, scope, type, name, snapshot_path, metadata_json, created_by, created_at, updated_at]
      properties:
        id: { type: string }
        team_id: { type: [string, "null"] }
        scope: { type: string, enum: [system, team, personal] }
        type: { type: string }
        name: { type: string }
        description: { type: [string, "null"] }
        snapshot_path: { type: string }
        metadata_json: { type: object, additionalProperties: true }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    ActivityEvent:
      type: object
      required: [id, resource_type, resource_id, event_type, metadata_json, created_at]
      properties:
        id: { type: string }
        actor_user_id: { type: [string, "null"] }
        team_id: { type: [string, "null"] }
        resource_type: { type: string }
        resource_id: { type: string }
        event_type: { type: string }
        metadata_json: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }
        actor:
          $ref: "#/components/schemas/User"
    TeamInvite:
      type: object
      required: [id, team_id, email, role, invited_by, expires_at, created_at]
      properties:
        id: { type: string }
        team_id: { type: string }
        email: { type: string, format: email }
        role: { type: string, enum: [admin, editor, viewer] }
        invited_by: { type: string }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    PermissionGrant:
      type: object
      required: [id, resource_type, resource_id, subject_type, subject_id, permission, created_at]
      properties:
        id: { type: string }
        resource_type: { type: string }
        resource_id: { type: string }
        subject_type: { type: string, enum: [user, team, link] }
        subject_id: { type: string }
        permission: { type: string, enum: [view, comment, edit, manage, share, invite] }
        inherited_from: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
    ShareLink:
      type: object
      required: [id, resource_type, resource_id, permission, created_by, created_at]
      properties:
        id: { type: string }
        resource_type: { type: string, enum: [drawing, folder, project] }
        resource_id: { type: string }
        permission: { type: string, enum: [view, comment, edit] }
        expires_at: { type: [string, "null"], format: date-time }
        created_by: { type: string }
        revoked_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
    DrawingAsset:
      type: object
      required: [id, drawing_id, kind, path, mime_type, size, uploaded_by, created_at]
      properties:
        id: { type: string }
        drawing_id: { type: string }
        kind: { type: string, enum: [image, export, attachment, thumbnail] }
        path: { type: string }
        mime_type: { type: string }
        size: { type: integer, format: int64 }
        width: { type: [integer, "null"] }
        height: { type: [integer, "null"] }
        uploaded_by: { type: string }
        created_at: { type: string, format: date-time }
    Embed:
      type: object
      required: [id, drawing_id, source_url, canonical_url, provider, embed_type, created_by, created_at]
      properties:
        id: { type: string }
        drawing_id: { type: string }
        source_url: { type: string, format: uri }
        canonical_url: { type: string, format: uri }
        provider: { type: string }
        embed_type: { type: string, enum: [link, iframe, provider] }
        title: { type: [string, "null"] }
        preview_asset_id: { type: [string, "null"] }
        safe_embed_html: { type: [string, "null"] }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
    LinkReference:
      type: object
      required: [id, source_resource_type, source_resource_id, target_resource_type, target_resource_id, created_by, created_at]
      properties:
        id: { type: string }
        source_resource_type: { type: string }
        source_resource_id: { type: string }
        target_resource_type: { type: string, enum: [drawing, folder, project, embed] }
        target_resource_id: { type: string }
        label: { type: [string, "null"] }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
    WorkspaceStats:
      type: object
      required: [teams, members, projects, folders, drawings, templates, revisions, assets, storage_bytes]
      properties:
        teams: { type: integer }
        members: { type: integer }
        projects: { type: integer }
        folders: { type: integer }
        drawings: { type: integer }
        templates: { type: integer }
        revisions: { type: integer }
        assets: { type: integer }
        storage_bytes: { type: integer, format: int64 }
    Project:
      type: object
      required: [id, team_id, name, slug, created_by, created_at, updated_at]
      properties:
        id: { type: string }
        team_id: { type: string }
        name: { type: string }
        slug: { type: string }
        description: { type: [string, "null"] }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Folder:
      type: object
      required: [id, team_id, name, slug, path_cache, visibility, created_by, created_at, updated_at]
      properties:
        id: { type: string }
        team_id: { type: string }
        project_id: { type: [string, "null"] }
        parent_folder_id: { type: [string, "null"] }
        name: { type: string }
        slug: { type: string }
        path_cache: { type: string }
        visibility: { type: string, enum: [private, team] }
        created_by: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
paths:
  /health:
    get:
      security: []
      summary: Health check
      responses:
        "200":
          description: Backend healthy
        "503":
          description: Backend unhealthy
  /auth/signup:
    post:
      security: []
      summary: Create password account
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password]
              properties:
                name: { type: string, minLength: 1, maxLength: 120 }
                email: { type: string, format: email }
                password: { type: string, minLength: 8, maxLength: 128 }
      responses:
        "201":
          description: Signed up
        "409":
          description: Email already exists
  /auth/login:
    post:
      security: []
      summary: Login with email and password
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        "200":
          description: Logged in
        "401":
          description: Invalid credentials
  /auth/logout:
    post:
      summary: Revoke current session
      responses:
        "200":
          description: Logged out
  /auth/me:
    get:
      summary: Current user
      responses:
        "200":
          description: Current user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
  /teams:
    get:
      summary: List accessible teams
      responses:
        "200":
          description: Teams
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Team"
    post:
      summary: Create team
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                slug: { type: string }
      responses:
        "201":
          description: Created team
  /teams/{teamID}/members:
    get:
      summary: List team members
      parameters:
        - in: path
          name: teamID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Members
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TeamMembership"
  /teams/{teamID}/invites:
    get:
      summary: List pending team invites
      parameters:
        - in: path
          name: teamID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Pending invites
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TeamInvite"
    post:
      summary: Create team invite
      parameters:
        - in: path
          name: teamID
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, role]
              properties:
                email: { type: string, format: email }
                role: { type: string, enum: [admin, editor, viewer] }
      responses:
        "201":
          description: Invite and one-time token
  /invites/accept:
    post:
      summary: Accept invite token as current user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }
      responses:
        "200":
          description: Accepted membership
  /drawings:
    get:
      summary: List drawings visible to current user
      parameters:
        - in: query
          name: team_id
          schema: { type: string }
      responses:
        "200":
          description: Drawings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Drawing"
    post:
      summary: Create drawing
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                team_id: { type: [string, "null"] }
                folder_id: { type: [string, "null"] }
                project_id: { type: [string, "null"] }
                title: { type: string }
                description: { type: [string, "null"] }
                visibility: { type: string }
                snapshot: {}
      responses:
        "201":
          description: Created drawing
  /drawings/{drawingID}:
    get:
      summary: Get drawing metadata
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Drawing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Drawing"
    patch:
      summary: Update drawing metadata
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Updated drawing
    delete:
      summary: Archive drawing
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Archived
  /drawings/{drawingID}/revisions:
    get:
      summary: List drawing revisions
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Revisions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/DrawingRevision"
    post:
      summary: Create immutable drawing revision
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Revision created
  /drawings/{drawingID}/permissions:
    get:
      summary: List explicit drawing permissions
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Permission grants
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PermissionGrant"
    post:
      summary: Grant explicit drawing permission
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created permission grant
  /drawings/{drawingID}/share-links:
    get:
      summary: List active drawing share links
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Share links without token hashes
    post:
      summary: Create drawing share link
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created share link and one-time token
  /shared/{token}:
    get:
      security: []
      summary: Resolve public share token
      parameters:
        - in: path
          name: token
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Shared resource payload
  /drawings/{drawingID}/assets:
    get:
      summary: List drawing asset metadata
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Assets
    post:
      summary: Create drawing asset metadata
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created asset metadata
  /drawings/{drawingID}/embeds:
    get:
      summary: List drawing embeds
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Embeds
    post:
      summary: Create safe drawing embed metadata
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created embed metadata
  /drawings/{drawingID}/links:
    get:
      summary: List drawing link references
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Link references
    post:
      summary: Create drawing link reference
      parameters:
        - in: path
          name: drawingID
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created link reference
  /templates:
    get:
      summary: List system and accessible team templates
      parameters:
        - in: query
          name: team_id
          schema: { type: string }
      responses:
        "200":
          description: Templates
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Template"
  /activity:
    get:
      summary: List recent activity for accessible teams
      parameters:
        - in: query
          name: team_id
          schema: { type: string }
      responses:
        "200":
          description: Activity
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ActivityEvent"
  /stats:
    get:
      summary: Workspace counts and storage usage
      parameters:
        - in: query
          name: team_id
          schema: { type: string }
      responses:
        "200":
          description: Workspace stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceStats"
  /folders:
    get:
      summary: List folders
      responses:
        "200":
          description: Folders
    post:
      summary: Create folder
      responses:
        "201":
          description: Created folder
  /projects:
    get:
      summary: List projects
      responses:
        "200":
          description: Projects
    post:
      summary: Create project
      responses:
        "201":
          description: Created project
