fix: code-driven OpenAPI docs — replace static JSON with swagger-jsdoc
Some checks failed
Deploy to Staging / build-and-deploy (push) Failing after 10m13s

BREAKING: OpenAPI spec is now generated from JSDoc annotations on route
handlers at startup, eliminating drift between code and documentation.

What was wrong:
- Static public/openapi.json was manually maintained and could drift
- Missing endpoints: signup, billing (checkout/success/webhook)
- Signup route was imported but never mounted (dead code)

What was fixed:
- Added swagger-jsdoc to generate OpenAPI spec from JSDoc on route files
- Every route handler now has @openapi JSDoc annotation as source of truth
- Spec served dynamically at GET /openapi.json (no static file)
- Deleted public/openapi.json
- Documented all missing endpoints (signup, billing x3)
- Mounted /v1/signup route
- All 9 screenshot params documented with types, ranges, defaults
This commit is contained in:
SnapAPI CEO 2026-02-20 07:32:37 +00:00
parent a70157d0ae
commit 713cc30ac7
10 changed files with 700 additions and 124 deletions

View file

@ -1,118 +0,0 @@
{
"openapi": "3.0.3",
"info": {
"title": "SnapAPI — Screenshot API",
"description": "Convert any URL to a pixel-perfect screenshot. EU-hosted, GDPR compliant.\n\n## Authentication\nAPI screenshot requests require an API key:\n- `Authorization: Bearer YOUR_API_KEY` header, or\n- `X-API-Key: YOUR_API_KEY` header\n\n## Playground\nThe `/v1/playground` endpoint requires no authentication but returns watermarked screenshots (5 requests/hour per IP).\n\n## Rate Limits\n- 120 requests per minute per IP (global)\n- 5 requests per hour per IP (playground)\n- Monthly screenshot limits based on your plan tier (API)",
"version": "0.3.0",
"contact": {
"name": "SnapAPI Support",
"url": "https://snapapi.eu",
"email": "support@snapapi.eu"
},
"license": {"name": "Proprietary"}
},
"servers": [{"url": "https://snapapi.eu", "description": "Production (EU — Germany)"}],
"tags": [
{"name": "Screenshots", "description": "Screenshot capture endpoints"},
{"name": "Playground", "description": "Free demo (no auth, watermarked)"},
{"name": "System", "description": "Health and status endpoints"}
],
"paths": {
"/v1/screenshot": {
"post": {
"tags": ["Screenshots"],
"summary": "Take a screenshot (authenticated)",
"description": "Capture a pixel-perfect, unwatermarked screenshot. Requires an API key.",
"operationId": "takeScreenshot",
"security": [{"BearerAuth": []}, {"ApiKeyAuth": []}],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ScreenshotRequest"},
"examples": {
"simple": {"summary": "Simple screenshot", "value": {"url": "https://example.com"}},
"hd_jpeg": {"summary": "HD JPEG", "value": {"url": "https://github.com", "format": "jpeg", "width": 1920, "height": 1080, "quality": 90}},
"mobile": {"summary": "Mobile", "value": {"url": "https://example.com", "width": 375, "height": 812, "deviceScale": 2}}
}
}
}
},
"responses": {
"200": {"description": "Screenshot captured", "content": {"image/png": {"schema": {"type": "string", "format": "binary"}}, "image/jpeg": {"schema": {"type": "string", "format": "binary"}}, "image/webp": {"schema": {"type": "string", "format": "binary"}}}},
"400": {"description": "Invalid request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"401": {"description": "Missing API key", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"403": {"description": "Invalid API key", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"429": {"description": "Rate/usage limit exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"503": {"description": "Service busy", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"504": {"description": "Timeout", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
}
}
},
"/v1/playground": {
"post": {
"tags": ["Playground"],
"summary": "Free demo screenshot (watermarked)",
"description": "Take a watermarked screenshot without authentication. Limited to 5 requests per hour per IP, max 1920x1080 resolution. Perfect for evaluating the API before purchasing a plan.",
"operationId": "playgroundScreenshot",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["url"],
"properties": {
"url": {"type": "string", "format": "uri", "description": "URL to capture", "example": "https://example.com"},
"format": {"type": "string", "enum": ["png", "jpeg", "webp"], "default": "png"},
"width": {"type": "integer", "minimum": 320, "maximum": 1920, "default": 1280},
"height": {"type": "integer", "minimum": 200, "maximum": 1080, "default": 800}
}
}
}
}
},
"responses": {
"200": {"description": "Watermarked screenshot", "content": {"image/png": {"schema": {"type": "string", "format": "binary"}}}},
"400": {"description": "Invalid request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"429": {"description": "Rate limit (5/hr)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"503": {"description": "Service busy", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
}
}
},
"/health": {
"get": {
"tags": ["System"],
"summary": "Health check",
"operationId": "healthCheck",
"responses": {
"200": {"description": "Healthy", "content": {"application/json": {"schema": {"type": "object", "properties": {"status": {"type": "string"}, "version": {"type": "string"}, "uptime": {"type": "number"}, "browser": {"type": "object"}}}}}}
}
}
}
},
"components": {
"securitySchemes": {
"BearerAuth": {"type": "http", "scheme": "bearer"},
"ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key"}
},
"schemas": {
"ScreenshotRequest": {
"type": "object",
"required": ["url"],
"properties": {
"url": {"type": "string", "format": "uri", "example": "https://example.com"},
"format": {"type": "string", "enum": ["png", "jpeg", "webp"], "default": "png"},
"width": {"type": "integer", "minimum": 320, "maximum": 3840, "default": 1280},
"height": {"type": "integer", "minimum": 200, "maximum": 2160, "default": 800},
"fullPage": {"type": "boolean", "default": false},
"quality": {"type": "integer", "minimum": 1, "maximum": 100, "default": 80},
"waitForSelector": {"type": "string"},
"deviceScale": {"type": "number", "minimum": 1, "maximum": 3, "default": 1},
"delay": {"type": "integer", "minimum": 0, "maximum": 5000, "default": 0}
}
},
"Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "string"}}}
}
}
}