{
  "openapi": "3.1.0",
  "info": {
    "title": "DesignPin REST API",
    "version": "1.0.0",
    "description": "REST API for DesignPin — review HTML prototypes with DOM-anchored comments. Programmatically push design versions, fetch reviewer feedback, and create quick-share review links. Designed for AI tools (ChatGPT, Claude, Gemini) and CI/CD pipelines.",
    "contact": {
      "name": "DesignPin Support",
      "email": "designpin.hello@gmail.com",
      "url": "https://designpin.pro"
    }
  },
  "servers": [
    { "url": "https://designpin.pro", "description": "Production" }
  ],
  "paths": {
    "/api/v1/quick-share": {
      "post": {
        "operationId": "createShareLink",
        "summary": "Create a public review link for an HTML prototype",
        "description": "Creates a public share URL for an HTML prototype. No auth needed. Rate-limited to 10 requests/hour per IP. Returns a review URL anyone can open without login. Use for one-shot sharing; for programmatic uploads to an existing project, use uploadVersion.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QuickShareRequest" },
              "example": {
                "html": "<!doctype html><html><body><h1>Hero copy V1</h1><p>Subhead text</p></body></html>",
                "title": "Landing page hero — variant A",
                "authorName": "Alex"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Share link created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QuickShareResponse" },
                "example": {
                  "url": "https://designpin.pro/review/EXAMPLE_TOKEN_xyz",
                  "reviewToken": "EXAMPLE_TOKEN_xyz",
                  "projectId": "proj_example_abc123",
                  "moduleId": "mod_example_def456",
                  "versionId": "ver_example_ghi789"
                }
              }
            }
          },
          "400": {
            "description": "Missing required field. Possible errors: 'html is required', 'title is required'.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "html is required" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 10 requests/hour per IP.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "Rate limit exceeded — 10 requests/hour per IP" }
              }
            }
          }
        }
      }
    },
    "/api/v1/push": {
      "post": {
        "operationId": "uploadVersion",
        "summary": "Upload a new version to an existing module",
        "description": "Uploads a new HTML version to an existing module within a project. Requires a project-scoped API key. New version is auto-assigned the next versionNumber. The API key's project must match the projectId in the request body; cross-project pushes return 403.",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PushRequest" },
              "example": {
                "html": "<!doctype html><html><body><h1>Hero copy V2</h1></body></html>",
                "projectId": "proj_example_abc123",
                "moduleId": "mod_example_def456",
                "authorName": "Claude (via MCP)",
                "description": "Revised hero per design feedback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Version uploaded successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PushResponse" },
                "example": {
                  "url": "https://designpin.pro/review/EXAMPLE_TOKEN_xyz",
                  "versionId": "ver_example_ghi789",
                  "versionNumber": "5"
                }
              }
            }
          },
          "400": {
            "description": "Missing required field (html, projectId, or moduleId).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "moduleId is required" }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "Invalid or missing API key" }
              }
            }
          },
          "403": {
            "description": "API key not authorized for the requested projectId.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "API key not authorized for this project" }
              }
            }
          },
          "404": {
            "description": "Module not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "Module not found" }
              }
            }
          }
        }
      }
    },
    "/api/v1/comments/{moduleId}/{versionId}": {
      "get": {
        "operationId": "listComments",
        "summary": "Fetch comments visible on a specific version",
        "description": "Returns comments visible on a version. A comment is visible if its origin version is equal to or earlier than the requested version (chronological cutoff). Comment placed on v1 stays visible on v2, v3, v4. Comment on v3 is not on v2 or v1. ProjectId is implicit from the API key.",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Firestore document ID of the module within the API key's project.",
            "example": "mod_example_def456"
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Firestore document ID of the version. Must exist within the module; otherwise 404.",
            "example": "ver_example_ghi789"
          }
        ],
        "responses": {
          "200": {
            "description": "Comments visible on the requested version. May be an empty array if no comments are chronologically eligible.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CommentsResponse" },
                "example": {
                  "comments": [
                    {
                      "id": "cmt_example_001",
                      "pinNumber": 1,
                      "text": "The CTA button needs more contrast — fails WCAG AA.",
                      "status": "open",
                      "author": "Priya Sharma",
                      "viewport": "lap",
                      "severity": "must",
                      "element": {
                        "selector": "button.cta-primary",
                        "textSnippet": "Get started"
                      },
                      "replies": []
                    },
                    {
                      "id": "cmt_example_002",
                      "pinNumber": 4,
                      "text": "Hero illustration could be larger on desktop.",
                      "status": "open",
                      "author": "Marcus Lee",
                      "viewport": "desk",
                      "severity": "nice",
                      "element": {
                        "selector": "section#hero img.illustration",
                        "textSnippet": null
                      },
                      "replies": [
                        { "text": "Agreed — let's bump to 480px width.", "author": "Priya Sharma" },
                        { "text": "Will update in v3.", "author": "Alex (designer)" }
                      ]
                    }
                  ],
                  "summary": "2 open, 0 resolved"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "Invalid or missing API key" }
              }
            }
          },
          "404": {
            "description": "Version not found in module.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": { "error": "Version not found in module" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dp_live_<32 alphanumeric>",
        "description": "Project-scoped API key. Generate at https://designpin.pro under Project Settings → API keys. Each key is shown once at generation. Format: dp_live_ followed by 32 alphanumeric characters. Send as: Authorization: Bearer dp_live_..."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message." }
        }
      },
      "QuickShareRequest": {
        "type": "object",
        "required": ["html", "title"],
        "properties": {
          "html":       { "type": "string", "description": "Complete, self-contained HTML document. Inline CSS recommended; external resources will not render in the iframe sandbox." },
          "title":      { "type": "string", "maxLength": 80, "description": "Display name for the share. Truncated server-side to 80 characters." },
          "authorName": { "type": "string", "maxLength": 80, "default": "API", "description": "Optional. Shown as the uploader on the review page. Truncated server-side to 80 characters." }
        }
      },
      "QuickShareResponse": {
        "type": "object",
        "required": ["url", "reviewToken", "projectId", "moduleId", "versionId"],
        "properties": {
          "url":         { "type": "string", "format": "uri", "description": "Public review URL. Anyone with the link can view and comment without login." },
          "reviewToken": { "type": "string", "description": "16-character alphanumeric token embedded in the URL." },
          "projectId":   { "type": "string", "description": "Firestore project document ID." },
          "moduleId":    { "type": "string", "description": "Firestore module document ID." },
          "versionId":   { "type": "string", "description": "Firestore version document ID." }
        }
      },
      "PushRequest": {
        "type": "object",
        "required": ["html", "projectId", "moduleId"],
        "properties": {
          "html":        { "type": "string", "description": "Complete HTML for the new version." },
          "projectId":   { "type": "string", "description": "Target project. MUST match the projectId of the API key being used; otherwise 403." },
          "moduleId":    { "type": "string", "description": "Target module within the project. Must already exist; otherwise 404." },
          "authorName":  { "type": "string", "description": "Optional. Defaults to the API key's owner name." },
          "description": { "type": "string", "description": "Optional. Stored on the version document and shown in the version sidebar." }
        }
      },
      "PushResponse": {
        "type": "object",
        "required": ["url", "versionId", "versionNumber"],
        "properties": {
          "url":           { "type": "string", "format": "uri", "description": "Project share URL. May be empty string for legacy projects without a share token." },
          "versionId":     { "type": "string", "description": "Firestore document ID of the new version." },
          "versionNumber": { "type": "string", "description": "Auto-incremented version number, stringified. The new version is the latest in the module." }
        }
      },
      "Comment": {
        "type": "object",
        "required": ["id", "pinNumber", "text", "status", "author", "severity", "replies"],
        "properties": {
          "id":         { "type": "string", "description": "Firestore document ID of the comment." },
          "pinNumber":  { "type": "integer", "minimum": 1, "description": "Module-scoped pin number. Starts at 1, never reused. Numbers may have gaps if comments were deleted." },
          "text":       { "type": "string", "description": "Comment body as written by the author." },
          "status":     { "type": "string", "enum": ["open", "resolved"], "description": "open = pending action; resolved = addressed by uploader." },
          "author":     { "type": "string", "description": "Display name of the commenter." },
          "viewport":   {
            "type": ["string", "null"],
            "enum": ["mob", "tab", "lap", "desk", "cus", null],
            "description": "Viewport on which the comment was placed. mob=Mobile, tab=Tablet, lap=Laptop, desk=Desktop, cus=Custom. May be null for legacy comments."
          },
          "severity":   { "type": "string", "enum": ["must", "should", "nice"], "description": "Severity tier: must=P0 must-fix, should=P1 should-fix, nice=P2 nice-to-have." },
          "element":    {
            "type": ["object", "null"],
            "description": "DOM anchor for the comment. null for coordinate-anchored comments.",
            "properties": {
              "selector":    { "type": "string", "description": "CSS selector targeting the anchored element." },
              "textSnippet": { "type": ["string", "null"], "description": "First ~100 characters of the element's text content at placement time." }
            }
          },
          "replies":    {
            "type": "array",
            "description": "Threaded replies on the comment. Empty array if none.",
            "items": {
              "type": "object",
              "required": ["text", "author"],
              "properties": {
                "text":   { "type": "string" },
                "author": { "type": "string" }
              }
            }
          }
        }
      },
      "CommentsResponse": {
        "type": "object",
        "required": ["comments", "summary"],
        "properties": {
          "comments": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Comment" }
          },
          "summary":  { "type": "string", "description": "Human-readable count summary like '3 open, 1 resolved'." }
        }
      }
    }
  }
}
