{"openapi":"3.0.3","info":{"title":"srj1.cc — URL Shortener API","version":"1.0.0","description":"A self-hosted URL shortener with custom slugs, click analytics, QR code generation, and optional link expiry.","contact":{"name":"Suraj Shetty","url":"https://surajshetty.com"}},"servers":[{"url":"https://srj1.cc","description":"Production"}],"security":[{"BearerAuth":[]},{"PasswordAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key obtained from POST /api/keys. Send as: Authorization: Bearer <key>"},"PasswordAuth":{"type":"apiKey","in":"header","name":"x-admin-password","description":"Admin or demo password sent in the x-admin-password header."}},"schemas":{"Link":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string","example":"book"},"destination":{"type":"string","format":"uri","example":"https://calendly.com/suraj"},"expires_at":{"type":"string","format":"date-time","nullable":true},"owner":{"type":"string","enum":["admin","demo"]},"created_at":{"type":"string","format":"date-time"},"click_count":{"type":"integer"}}},"Error":{"type":"object","properties":{"error":{"type":"string"}}},"ClickStats":{"type":"object","properties":{"total":{"type":"integer"},"last_7_days":{"type":"integer"},"daily":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"count":{"type":"integer"}}}}}},"ApiKey":{"type":"object","properties":{"id":{"type":"integer"},"key":{"type":"string","description":"Full key on creation, masked on list"},"name":{"type":"string"},"role":{"type":"string","enum":["admin","demo"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/links":{"post":{"summary":"Create a short link","tags":["Links"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","destination"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9_-]+$","minLength":2,"maxLength":50,"example":"book"},"destination":{"type":"string","format":"uri","example":"https://calendly.com/suraj"},"expires_at":{"type":"string","format":"date-time","nullable":true}}}}}},"responses":{"201":{"description":"Link created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List all links with click counts","tags":["Links"],"responses":{"200":{"description":"List of links","content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string"},"links":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}}}}}}}},"/api/links/{slug}":{"put":{"summary":"Update a link's destination or expiry","tags":["Links"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"destination":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time","nullable":true}}}}}},"responses":{"200":{"description":"Link updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"403":{"description":"Cannot edit links owned by another role"},"404":{"description":"Link not found"}}},"delete":{"summary":"Delete a link and its click history","tags":["Links"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"403":{"description":"Cannot delete links owned by another role"},"404":{"description":"Link not found"}}}},"/api/links/{slug}/stats":{"get":{"summary":"Get click analytics for a link (last 14 days)","tags":["Analytics"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Click stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClickStats"}}}},"404":{"description":"Link not found"}}}},"/api/links/{slug}/qr":{"get":{"summary":"Generate a QR code PNG data URL for a link","tags":["Analytics"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"QR code","content":{"application/json":{"schema":{"type":"object","properties":{"qr":{"type":"string","description":"PNG data URL"},"url":{"type":"string","format":"uri"}}}}}}}}},"/api/keys":{"post":{"summary":"Create an API key (admin only)","tags":["API Keys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"My CRM integration"},"role":{"type":"string","enum":["admin","demo"],"default":"demo"}}}}}},"responses":{"201":{"description":"Key created (full key returned only here)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"403":{"description":"Admin access required"}}},"get":{"summary":"List all API keys (admin only, keys masked)","tags":["API Keys"],"responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}}},"/api/keys/{id}":{"delete":{"summary":"Revoke an API key (admin only)","tags":["API Keys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Key revoked"},"403":{"description":"Admin access required"}}}},"/{slug}":{"get":{"summary":"Redirect to destination URL (public, no auth)","tags":["Redirect"],"security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to destination URL"},"404":{"description":"Link not found"},"410":{"description":"Link expired"}}}}}}