{"openapi":"3.1.0","info":{"title":"OrphLink API","version":"1.0.0","description":"Multi-tenant link shortener and proxy platform API"},"components":{"schemas":{"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"ts":{"type":"integer"}},"required":["status","ts"]},"ValidationIssue":{"type":"object","properties":{"path":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]},"example":["destination_url"]},"code":{"type":"string","example":"invalid_string"},"message":{"type":"string","example":"Invalid url"}},"required":["path","code","message"]},"Error":{"type":"object","properties":{"error":{"type":"string","example":"Not Found"},"code":{"type":"string","example":"NOT_FOUND"},"status":{"type":"integer","example":404},"details":{"type":"array","items":{"$ref":"#/components/schemas/ValidationIssue"}}},"required":["error","code","status"]},"ReadyResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ready","degraded"]},"ts":{"type":"integer"},"checks":{"type":"object","properties":{"d1":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"kv":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"r2":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"moderation_queue":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"webhook_queue":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"analytics":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":"number"},"error":{"type":"string"}},"required":["ok"]}},"required":["d1","kv","r2","moderation_queue","webhook_queue","analytics"]}},"required":["status","ts","checks"]},"AuthResponse":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string","example":"usr_abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"display_name":{"type":"string","example":"Jane Doe"}},"required":["id","email","display_name"]},"access_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs..."},"refresh_token":{"type":"string","example":"rt_abc123..."}},"required":["user","access_token","refresh_token"]},"RegisterInput":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"password":{"type":"string","minLength":8,"example":"securepass123"},"display_name":{"type":"string","minLength":1,"maxLength":100,"example":"Jane Doe"},"cf_turnstile_response":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token","example":"0.AbCdEf..."}},"required":["email","password","display_name"]},"LoginResponse":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string","example":"usr_abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"display_name":{"type":"string","example":"Jane Doe"}},"required":["id","email","display_name"]},"access_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs..."},"refresh_token":{"type":"string","example":"rt_abc123..."},"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"ws_abc123"},"name":{"type":"string","example":"My Workspace"},"slug":{"type":"string","example":"my-workspace"},"role":{"type":"string","enum":["owner","admin","member"],"example":"owner"}},"required":["id","name","slug","role"]}}},"required":["user","access_token","refresh_token","workspaces"]},"LoginInput":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"password":{"type":"string","minLength":1,"example":"securepass123"},"cf_turnstile_response":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token","example":"0.AbCdEf..."}},"required":["email","password"]},"TokenPairResponse":{"type":"object","properties":{"access_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs..."},"refresh_token":{"type":"string","example":"rt_abc123..."}},"required":["access_token","refresh_token"]},"RefreshInput":{"type":"object","properties":{"refresh_token":{"type":"string","example":"rt_abc123..."}},"required":["refresh_token"]},"Success":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]}},"required":["success"]},"LogoutInput":{"type":"object","properties":{"refresh_token":{"type":"string","example":"rt_abc123..."}},"required":["refresh_token"]},"UserProfile":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string","example":"usr_abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"display_name":{"type":"string","example":"Jane Doe"},"avatar_url":{"type":"string","nullable":true,"format":"uri","example":"https://example.com/avatar.png"}},"required":["id","email","display_name","avatar_url"]},"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"ws_abc123"},"name":{"type":"string","example":"My Workspace"},"slug":{"type":"string","example":"my-workspace"},"role":{"type":"string","enum":["owner","admin","member"],"example":"owner"}},"required":["id","name","slug","role"]}}},"required":["user","workspaces"]},"UpdateProfileInput":{"type":"object","properties":{"display_name":{"type":"string","minLength":1,"maxLength":120},"name":{"type":"string","minLength":1,"maxLength":120},"avatar_url":{"type":"string","nullable":true,"format":"uri"}}},"ChangePasswordInput":{"type":"object","properties":{"current_password":{"type":"string","minLength":1},"currentPassword":{"type":"string","minLength":1},"new_password":{"type":"string","minLength":8},"newPassword":{"type":"string","minLength":8}}},"SelectWorkspaceInput":{"type":"object","properties":{"auth_token":{"type":"string","minLength":1},"workspace_id":{"type":"string","minLength":1}},"required":["auth_token","workspace_id"]},"ForgotPasswordInput":{"type":"object","properties":{"email":{"type":"string","format":"email"},"cf_turnstile_response":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token","example":"0.AbCdEf..."}},"required":["email"]},"ResetPasswordInput":{"type":"object","properties":{"token":{"type":"string","minLength":1},"password":{"type":"string","minLength":8}},"required":["token","password"]},"WaitlistResponse":{"type":"object","properties":{"message":{"type":"string","example":"You're on the list — we'll be in touch."}},"required":["message"]},"WaitlistInput":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","example":"user@example.com"},"source":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:-]+$","example":"marketing-home"},"cf_turnstile_response":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token","example":"0.AbCdEf..."}},"required":["email"]},"WorkspaceListResponse":{"type":"object","properties":{"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"ws_abc123"},"name":{"type":"string","example":"Marketing Team"},"slug":{"type":"string","example":"marketing-team"},"description":{"type":"string","nullable":true,"example":"Links for the marketing department"},"allow_public_links":{"type":"boolean","example":true},"recycle_expired_slugs":{"type":"boolean","example":false},"default_redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"interstitial_delay_seconds":{"type":"integer","example":5},"landing_page_enabled":{"type":"boolean","example":false},"gdpr_consent_enabled":{"type":"boolean","example":false},"gdpr_consent_message":{"type":"string","nullable":true,"example":null},"fraud_click_threshold":{"type":"integer","example":20},"embed_widgets_enabled":{"type":"boolean","example":false},"audit_log_retention_days":{"type":"integer","example":90},"ai_auto_tag_enabled":{"type":"boolean","example":false},"conversion_attribution_enabled":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"role":{"type":"string","enum":["owner","admin","member"],"example":"owner"}},"required":["id","name","slug","description","allow_public_links","recycle_expired_slugs","default_redirect_mode","interstitial_delay_seconds","created_at","role"]}}},"required":["workspaces"]},"WorkspaceResponse":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string","example":"ws_abc123"},"name":{"type":"string","example":"Marketing Team"},"slug":{"type":"string","example":"marketing-team"},"description":{"type":"string","nullable":true,"example":"Links for the marketing department"},"allow_public_links":{"type":"boolean","example":true},"recycle_expired_slugs":{"type":"boolean","example":false},"default_redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"interstitial_delay_seconds":{"type":"integer","example":5},"landing_page_enabled":{"type":"boolean","example":false},"gdpr_consent_enabled":{"type":"boolean","example":false},"gdpr_consent_message":{"type":"string","nullable":true,"example":null},"fraud_click_threshold":{"type":"integer","example":20},"embed_widgets_enabled":{"type":"boolean","example":false},"audit_log_retention_days":{"type":"integer","example":90},"ai_auto_tag_enabled":{"type":"boolean","example":false},"conversion_attribution_enabled":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","name","slug","description","allow_public_links","recycle_expired_slugs","default_redirect_mode","interstitial_delay_seconds","created_at"]}},"required":["workspace"]},"CreateWorkspaceInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Marketing Team"},"slug":{"type":"string","minLength":1,"maxLength":50,"pattern":"^[a-z0-9-]+$","example":"marketing-team"},"description":{"type":"string","maxLength":500,"example":"Links for the marketing department"},"allow_public_links":{"type":"boolean","example":true}},"required":["name"]},"UpdateWorkspaceInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Marketing Team v2"},"description":{"type":"string","maxLength":500,"example":"Updated description"},"allow_public_links":{"type":"boolean"},"recycle_expired_slugs":{"type":"boolean"},"default_redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"interstitial_delay_seconds":{"type":"integer","minimum":0,"maximum":30,"example":5},"landing_page_enabled":{"type":"boolean"},"gdpr_consent_enabled":{"type":"boolean"},"gdpr_consent_message":{"type":"string","nullable":true,"maxLength":2000},"fraud_click_threshold":{"type":"integer","minimum":1,"maximum":10000},"fraud_threshold":{"type":"integer","minimum":1,"maximum":10000},"embed_widgets_enabled":{"type":"boolean"},"audit_log_retention_days":{"type":"integer","minimum":1,"maximum":3650},"audit_log_retention":{"type":"integer","minimum":1,"maximum":3650},"ai_auto_tag_enabled":{"type":"boolean"},"conversion_attribution_enabled":{"type":"boolean"}}},"MemberResponse":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","example":"usr_abc123"},"email":{"type":"string","nullable":true,"format":"email","example":"member@example.com"},"display_name":{"type":"string","example":"Jane Doe"},"role":{"type":"string","enum":["owner","admin","member"],"example":"member"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["user_id","email","display_name","role","created_at"]}}},"required":["members"]},"AddMemberInput":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"newmember@example.com"},"role":{"type":"string","enum":["owner","admin","member"],"example":"member"}},"required":["email","role"]},"UpdateMemberInput":{"type":"object","properties":{"role":{"type":"string","enum":["owner","admin","member"],"example":"admin"}},"required":["role"]},"BrandingResponse":{"type":"object","properties":{"branding":{"type":"object","properties":{"logo_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/logo.png"},"favicon_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/favicon.ico"},"logo_alt_text":{"type":"string","nullable":true,"example":"A violet chain-link symbol on a dark field"},"favicon_alt_text":{"type":"string","nullable":true,"example":"A small violet link icon"},"primary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"},"tagline":{"type":"string","nullable":true,"example":"Powered by OrphLink"},"interstitial_message":{"type":"string","nullable":true,"example":"You are being redirected..."},"custom_css":{"type":"string","nullable":true,"example":""}},"required":["logo_url","favicon_url","logo_alt_text","favicon_alt_text","primary_color","secondary_color","background_color","text_color","tagline","interstitial_message","custom_css"]}},"required":["branding"]},"UpdateBrandingInput":{"type":"object","properties":{"primary_color":{"type":"string","maxLength":20,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","maxLength":20,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","maxLength":20,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","maxLength":20,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"},"tagline":{"type":"string","maxLength":200,"example":"Powered by OrphLink"},"interstitial_message":{"type":"string","maxLength":500,"example":"You are being redirected..."},"logo_alt_text":{"type":"string","nullable":true,"maxLength":200,"example":"Acme Corp logo"},"favicon_alt_text":{"type":"string","nullable":true,"maxLength":200,"example":"Acme favicon"},"custom_css":{"type":"string","maxLength":5000,"example":"body { font-family: sans-serif; }"}}},"UploadAssetResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"example":"https://orphl.ink/r2/ws_abc123/logo.png"},"branding":{"type":"object","properties":{"logo_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/logo.png"},"favicon_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/favicon.ico"},"logo_alt_text":{"type":"string","nullable":true,"example":"A violet chain-link symbol on a dark field"},"favicon_alt_text":{"type":"string","nullable":true,"example":"A small violet link icon"},"primary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"},"tagline":{"type":"string","nullable":true,"example":"Powered by OrphLink"},"interstitial_message":{"type":"string","nullable":true,"example":"You are being redirected..."},"custom_css":{"type":"string","nullable":true,"example":""}},"required":["logo_url","favicon_url","logo_alt_text","favicon_alt_text","primary_color","secondary_color","background_color","text_color","tagline","interstitial_message","custom_css"]}},"required":["url","branding"]},"ThemePreset":{"type":"object","properties":{"id":{"type":"string","example":"builtin:dark"},"name":{"type":"string","example":"Dark"},"primary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"secondary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#a5b4fc"},"background_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#0f172a"},"text_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#e2e8f0"},"is_built_in":{"type":"boolean","example":true},"created_at":{"type":"string","nullable":true,"example":null}},"required":["id","name","primary_color","secondary_color","background_color","text_color","is_built_in","created_at"]},"ThemePresetListResponse":{"type":"object","properties":{"presets":{"type":"array","items":{"$ref":"#/components/schemas/ThemePreset"}}},"required":["presets"]},"ThemePresetResponse":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/ThemePreset"}},"required":["preset"]},"CreateThemePresetInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"example":"Brand primary"},"primary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"}},"required":["name","primary_color","secondary_color","background_color","text_color"]},"UpdateThemePresetInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"example":"Brand primary"},"primary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"}}},"ApplyThemePresetResponse":{"type":"object","properties":{"preset_id":{"type":"string","example":"builtin:dark"},"branding":{"type":"object","properties":{"logo_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/logo.png"},"favicon_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/ws_abc123/favicon.ico"},"logo_alt_text":{"type":"string","nullable":true,"example":"A violet chain-link symbol on a dark field"},"favicon_alt_text":{"type":"string","nullable":true,"example":"A small violet link icon"},"primary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#6366f1"},"secondary_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#818cf8"},"background_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"text_color":{"type":"string","nullable":true,"pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#1f2937"},"tagline":{"type":"string","nullable":true,"example":"Powered by OrphLink"},"interstitial_message":{"type":"string","nullable":true,"example":"You are being redirected..."},"custom_css":{"type":"string","nullable":true,"example":""}},"required":["logo_url","favicon_url","logo_alt_text","favicon_alt_text","primary_color","secondary_color","background_color","text_color","tagline","interstitial_message","custom_css"]}},"required":["preset_id","branding"]},"UtmTemplate":{"type":"object","properties":{"id":{"type":"string","example":"utm_abc123"},"name":{"type":"string","example":"Spring newsletter"},"utm_source":{"type":"string","nullable":true,"maxLength":200,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":200,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":200,"example":"spring2026"},"utm_term":{"type":"string","nullable":true,"maxLength":200,"example":null},"utm_content":{"type":"string","nullable":true,"maxLength":200,"example":null},"created_at":{"type":"string","nullable":true,"example":"2026-06-03T00:00:00.000Z"}},"required":["id","name","utm_source","utm_medium","utm_campaign","utm_term","utm_content","created_at"]},"UtmTemplateListResponse":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/UtmTemplate"}}},"required":["templates"]},"UtmTemplateResponse":{"type":"object","properties":{"template":{"$ref":"#/components/schemas/UtmTemplate"}},"required":["template"]},"CreateUtmTemplateInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"example":"Spring newsletter"},"utm_source":{"type":"string","nullable":true,"maxLength":200,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":200,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":200,"example":"spring2026"},"utm_term":{"type":"string","nullable":true,"maxLength":200,"example":null},"utm_content":{"type":"string","nullable":true,"maxLength":200,"example":null}},"required":["name"]},"UpdateUtmTemplateInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"example":"Spring newsletter"},"utm_source":{"type":"string","nullable":true,"maxLength":200,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":200,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":200,"example":"spring2026"},"utm_term":{"type":"string","nullable":true,"maxLength":200,"example":null},"utm_content":{"type":"string","nullable":true,"maxLength":200,"example":null}}},"ConversionResponse":{"type":"object","properties":{"conversion":{"type":"object","properties":{"id":{"type":"string","example":"cnv_abc123"},"linkId":{"type":"string","example":"lnk_abc123"},"clickId":{"type":"string","example":"9f1c2e6a-7b3d-4c8e-9a1f-2b3c4d5e6f70"},"value":{"type":"number","nullable":true,"example":49.99},"currency":{"type":"string","nullable":true,"example":"GBP"},"convertedAt":{"type":"string","example":"2026-06-03T10:30:00.000Z"}},"required":["id","linkId","clickId","value","currency","convertedAt"]},"created":{"type":"boolean","example":true}},"required":["conversion","created"]},"RecordConversionInput":{"type":"object","properties":{"clickId":{"type":"string","minLength":1,"maxLength":128,"description":"The olc click id from the redirect","example":"9f1c2e6a-7b3d-4c8e-9a1f-2b3c4d5e6f70"},"value":{"type":"number","minimum":0,"description":"Conversion value","example":49.99},"currency":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Za-z]{3}$","example":"GBP"},"metadata":{"type":"object","additionalProperties":{"nullable":true},"example":{"orderId":"ord_123"}}},"required":["clickId"]},"SemanticSearchResponse":{"type":"object","properties":{"mode":{"type":"string","enum":["vector","keyword"],"example":"vector"},"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","nullable":true,"example":"Example Article"},"description":{"type":"string","nullable":true,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"type":"integer","example":302},"is_public":{"type":"boolean","example":false},"moderation_status":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"],"example":"approved"},"status":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"example":"active"},"health_status":{"type":"string","enum":["healthy","broken","unknown"],"example":"unknown"},"category":{"type":"string","nullable":true,"example":"news"},"is_featured":{"type":"boolean","example":false},"featured":{"type":"boolean","example":false},"click_count":{"type":"integer","minimum":0,"example":42},"unique_visitors":{"type":"integer","minimum":0,"description":"Omitted until per-link unique-visitor tracking exists.","example":0},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":null},"has_password":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"example":null},"utm_medium":{"type":"string","nullable":true,"example":null},"utm_campaign":{"type":"string","nullable":true,"example":null},"utm_term":{"type":"string","nullable":true,"example":null},"utm_content":{"type":"string","nullable":true,"example":null},"og_title":{"type":"string","nullable":true,"example":null},"og_description":{"type":"string","nullable":true,"example":null},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":null},"og_image_generated_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"has_variants":{"type":"boolean","example":false},"has_device_targets":{"type":"boolean","example":false},"has_geo_targets":{"type":"boolean","example":false},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":null},"is_active":{"type":"boolean","example":true},"group":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"}},"required":["id","name"]},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"created_by":{"type":"string","nullable":true,"example":"usr_abc123"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"score":{"type":"number","nullable":true,"minimum":0,"maximum":1,"example":0.91}},"required":["id","slug","short_url","destination_url","title","description","redirect_mode","redirect_code","is_public","moderation_status","status","health_status","category","is_featured","featured","click_count","last_clicked_at","has_password","utm_source","utm_medium","utm_campaign","utm_term","utm_content","og_title","og_description","og_image_url","og_image_generated_url","has_variants","has_device_targets","has_geo_targets","expires_at","activate_at","is_active","group","tags","created_by","created_at","updated_at","score"]}}},"required":["mode","links"]},"SimilarLinksResponse":{"type":"object","properties":{"vectorize_enabled":{"type":"boolean","example":true},"similar":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","nullable":true,"example":"Example Article"},"description":{"type":"string","nullable":true,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"type":"integer","example":302},"is_public":{"type":"boolean","example":false},"moderation_status":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"],"example":"approved"},"status":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"example":"active"},"health_status":{"type":"string","enum":["healthy","broken","unknown"],"example":"unknown"},"category":{"type":"string","nullable":true,"example":"news"},"is_featured":{"type":"boolean","example":false},"featured":{"type":"boolean","example":false},"click_count":{"type":"integer","minimum":0,"example":42},"unique_visitors":{"type":"integer","minimum":0,"description":"Omitted until per-link unique-visitor tracking exists.","example":0},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":null},"has_password":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"example":null},"utm_medium":{"type":"string","nullable":true,"example":null},"utm_campaign":{"type":"string","nullable":true,"example":null},"utm_term":{"type":"string","nullable":true,"example":null},"utm_content":{"type":"string","nullable":true,"example":null},"og_title":{"type":"string","nullable":true,"example":null},"og_description":{"type":"string","nullable":true,"example":null},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":null},"og_image_generated_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"has_variants":{"type":"boolean","example":false},"has_device_targets":{"type":"boolean","example":false},"has_geo_targets":{"type":"boolean","example":false},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":null},"is_active":{"type":"boolean","example":true},"group":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"}},"required":["id","name"]},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"created_by":{"type":"string","nullable":true,"example":"usr_abc123"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"score":{"type":"number","nullable":true,"minimum":0,"maximum":1,"example":0.91}},"required":["id","slug","short_url","destination_url","title","description","redirect_mode","redirect_code","is_public","moderation_status","status","health_status","category","is_featured","featured","click_count","last_clicked_at","has_password","utm_source","utm_medium","utm_campaign","utm_term","utm_content","og_title","og_description","og_image_url","og_image_generated_url","has_variants","has_device_targets","has_geo_targets","expires_at","activate_at","is_active","group","tags","created_by","created_at","updated_at","score"]}}},"required":["vectorize_enabled","similar"]},"BackfillEmbeddingsResponse":{"type":"object","properties":{"enqueued":{"type":"integer","minimum":0,"example":200},"vectorize_enabled":{"type":"boolean","example":true}},"required":["enqueued","vectorize_enabled"]},"BackfillEmbeddingsInput":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":500,"default":200}}},"BulkCreateItemError":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"example":3},"destination_url":{"type":"string","example":"http://10.0.0.5/internal"},"code":{"type":"string","example":"UNSAFE_DESTINATION_URL"},"error":{"type":"string","example":"Destination URL is not allowed (private_ip)"}},"required":["index","destination_url","code","error"]},"BulkCreateResponse":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","nullable":true,"example":"Example Article"},"description":{"type":"string","nullable":true,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"type":"integer","example":302},"is_public":{"type":"boolean","example":false},"moderation_status":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"],"example":"approved"},"status":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"example":"active"},"health_status":{"type":"string","enum":["healthy","broken","unknown"],"example":"unknown"},"category":{"type":"string","nullable":true,"example":"news"},"is_featured":{"type":"boolean","example":false},"featured":{"type":"boolean","example":false},"click_count":{"type":"integer","minimum":0,"example":42},"unique_visitors":{"type":"integer","minimum":0,"description":"Omitted until per-link unique-visitor tracking exists.","example":0},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":null},"has_password":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"example":null},"utm_medium":{"type":"string","nullable":true,"example":null},"utm_campaign":{"type":"string","nullable":true,"example":null},"utm_term":{"type":"string","nullable":true,"example":null},"utm_content":{"type":"string","nullable":true,"example":null},"og_title":{"type":"string","nullable":true,"example":null},"og_description":{"type":"string","nullable":true,"example":null},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":null},"og_image_generated_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"has_variants":{"type":"boolean","example":false},"has_device_targets":{"type":"boolean","example":false},"has_geo_targets":{"type":"boolean","example":false},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":null},"is_active":{"type":"boolean","example":true},"group":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"}},"required":["id","name"]},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"created_by":{"type":"string","nullable":true,"example":"usr_abc123"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","slug","short_url","destination_url","title","description","redirect_mode","redirect_code","is_public","moderation_status","status","health_status","category","is_featured","featured","click_count","last_clicked_at","has_password","utm_source","utm_medium","utm_campaign","utm_term","utm_content","og_title","og_description","og_image_url","og_image_generated_url","has_variants","has_device_targets","has_geo_targets","expires_at","activate_at","is_active","group","tags","created_by","created_at","updated_at"]}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/BulkCreateItemError"}},"meta":{"type":"object","properties":{"page":{"type":"integer","example":1},"per_page":{"type":"integer","minimum":0,"example":2},"total":{"type":"integer","minimum":0,"example":2}},"required":["page","per_page","total"]}},"required":["links","errors","meta"]},"BulkCreateInput":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","maxLength":200,"example":"Example Article"},"description":{"type":"string","maxLength":1000,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"anyOf":[{"type":"number","enum":[301]},{"type":"number","enum":[302]}],"example":302},"password":{"type":"string","minLength":1,"example":"secretpass"},"expires_at":{"type":"string","format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","format":"date-time","example":"2026-07-01T09:00:00Z"},"tag_ids":{"type":"array","items":{"type":"string"},"example":["tag_abc","tag_def"]},"group_id":{"type":"string","example":"grp_abc123"},"is_public":{"type":"boolean","example":false},"og_title":{"type":"string","nullable":true,"maxLength":200,"example":"Check out this article"},"og_description":{"type":"string","nullable":true,"maxLength":500,"example":"A deep dive into edge-native link shortening."},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":"https://cdn.example.com/preview.png"},"is_featured":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"maxLength":255,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":255,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":255,"example":"autumn-launch"},"utm_term":{"type":"string","nullable":true,"maxLength":255,"example":"short links"},"utm_content":{"type":"string","nullable":true,"maxLength":255,"example":"hero-cta"}},"required":["destination_url"]},"minItems":1,"maxItems":100}},"required":["links"]},"ImportLinksResponse":{"type":"object","properties":{"created":{"type":"integer","minimum":0,"example":2},"links":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer","example":2},"id":{"type":"string"},"slug":{"type":"string","example":"abc123"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/abc123"}},"required":["row","id","slug","short_url"]}},"errors":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer","example":3},"error":{"type":"string","example":"destination_url: Invalid url"}},"required":["row","error"]}}},"required":["created","links","errors"]},"PaginationMeta":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"example":1},"per_page":{"type":"integer","minimum":1,"example":25},"total":{"type":"integer","minimum":0,"example":100}},"required":["page","per_page","total"]},"LinkListResponse":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","nullable":true,"example":"Example Article"},"description":{"type":"string","nullable":true,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"type":"integer","example":302},"is_public":{"type":"boolean","example":false},"moderation_status":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"],"example":"approved"},"status":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"example":"active"},"health_status":{"type":"string","enum":["healthy","broken","unknown"],"example":"unknown"},"category":{"type":"string","nullable":true,"example":"news"},"is_featured":{"type":"boolean","example":false},"featured":{"type":"boolean","example":false},"click_count":{"type":"integer","minimum":0,"example":42},"unique_visitors":{"type":"integer","minimum":0,"description":"Omitted until per-link unique-visitor tracking exists.","example":0},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":null},"has_password":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"example":null},"utm_medium":{"type":"string","nullable":true,"example":null},"utm_campaign":{"type":"string","nullable":true,"example":null},"utm_term":{"type":"string","nullable":true,"example":null},"utm_content":{"type":"string","nullable":true,"example":null},"og_title":{"type":"string","nullable":true,"example":null},"og_description":{"type":"string","nullable":true,"example":null},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":null},"og_image_generated_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"has_variants":{"type":"boolean","example":false},"has_device_targets":{"type":"boolean","example":false},"has_geo_targets":{"type":"boolean","example":false},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":null},"is_active":{"type":"boolean","example":true},"group":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"}},"required":["id","name"]},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"created_by":{"type":"string","nullable":true,"example":"usr_abc123"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","slug","short_url","destination_url","title","description","redirect_mode","redirect_code","is_public","moderation_status","status","health_status","category","is_featured","featured","click_count","last_clicked_at","has_password","utm_source","utm_medium","utm_campaign","utm_term","utm_content","og_title","og_description","og_image_url","og_image_generated_url","has_variants","has_device_targets","has_geo_targets","expires_at","activate_at","is_active","group","tags","created_by","created_at","updated_at"]}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["links","meta"]},"LinkResponse":{"type":"object","properties":{"link":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"title":{"type":"string","nullable":true,"example":"Example Article"},"description":{"type":"string","nullable":true,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"type":"integer","example":302},"is_public":{"type":"boolean","example":false},"moderation_status":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"],"example":"approved"},"status":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"example":"active"},"health_status":{"type":"string","enum":["healthy","broken","unknown"],"example":"unknown"},"category":{"type":"string","nullable":true,"example":"news"},"is_featured":{"type":"boolean","example":false},"featured":{"type":"boolean","example":false},"click_count":{"type":"integer","minimum":0,"example":42},"unique_visitors":{"type":"integer","minimum":0,"description":"Omitted until per-link unique-visitor tracking exists.","example":0},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":null},"has_password":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"example":null},"utm_medium":{"type":"string","nullable":true,"example":null},"utm_campaign":{"type":"string","nullable":true,"example":null},"utm_term":{"type":"string","nullable":true,"example":null},"utm_content":{"type":"string","nullable":true,"example":null},"og_title":{"type":"string","nullable":true,"example":null},"og_description":{"type":"string","nullable":true,"example":null},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":null},"og_image_generated_url":{"type":"string","nullable":true,"format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"has_variants":{"type":"boolean","example":false},"has_device_targets":{"type":"boolean","example":false},"has_geo_targets":{"type":"boolean","example":false},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":null},"is_active":{"type":"boolean","example":true},"group":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"}},"required":["id","name"]},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"created_by":{"type":"string","nullable":true,"example":"usr_abc123"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","slug","short_url","destination_url","title","description","redirect_mode","redirect_code","is_public","moderation_status","status","health_status","category","is_featured","featured","click_count","last_clicked_at","has_password","utm_source","utm_medium","utm_campaign","utm_term","utm_content","og_title","og_description","og_image_url","og_image_generated_url","has_variants","has_device_targets","has_geo_targets","expires_at","activate_at","is_active","group","tags","created_by","created_at","updated_at"]}},"required":["link"]},"CreateLinkInput":{"type":"object","properties":{"destination_url":{"type":"string","format":"uri","example":"https://example.com/long-article-path"},"slug":{"type":"string","minLength":1,"maxLength":50,"pattern":"^[a-zA-Z0-9_-]{1,50}$","example":"my-link"},"title":{"type":"string","maxLength":200,"example":"Example Article"},"description":{"type":"string","maxLength":1000,"example":"A great article about..."},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"],"example":"direct"},"redirect_code":{"anyOf":[{"type":"number","enum":[301]},{"type":"number","enum":[302]}],"example":302},"password":{"type":"string","minLength":1,"example":"secretpass"},"expires_at":{"type":"string","format":"date-time","example":"2026-12-31T23:59:59Z"},"activate_at":{"type":"string","format":"date-time","example":"2026-07-01T09:00:00Z"},"tag_ids":{"type":"array","items":{"type":"string"},"example":["tag_abc","tag_def"]},"group_id":{"type":"string","example":"grp_abc123"},"is_public":{"type":"boolean","example":false},"og_title":{"type":"string","nullable":true,"maxLength":200,"example":"Check out this article"},"og_description":{"type":"string","nullable":true,"maxLength":500,"example":"A deep dive into edge-native link shortening."},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":"https://cdn.example.com/preview.png"},"is_featured":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"maxLength":255,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":255,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":255,"example":"autumn-launch"},"utm_term":{"type":"string","nullable":true,"maxLength":255,"example":"short links"},"utm_content":{"type":"string","nullable":true,"maxLength":255,"example":"hero-cta"}},"required":["destination_url"]},"UpdateLinkInput":{"type":"object","properties":{"destination_url":{"type":"string","format":"uri","example":"https://example.com/updated-path"},"title":{"type":"string","maxLength":200,"example":"Updated Title"},"description":{"type":"string","maxLength":1000,"example":"Updated description"},"redirect_mode":{"type":"string","enum":["direct","interstitial","proxy"]},"redirect_code":{"anyOf":[{"type":"number","enum":[301]},{"type":"number","enum":[302]}]},"password":{"type":"string","nullable":true,"minLength":1},"expires_at":{"type":"string","nullable":true,"format":"date-time"},"activate_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-07-01T09:00:00Z"},"is_active":{"type":"boolean"},"tag_ids":{"type":"array","items":{"type":"string"}},"group_id":{"type":"string","nullable":true},"og_title":{"type":"string","nullable":true,"maxLength":200,"example":"Check out this article"},"og_description":{"type":"string","nullable":true,"maxLength":500,"example":"A deep dive into edge-native link shortening."},"og_image_url":{"type":"string","nullable":true,"format":"uri","example":"https://cdn.example.com/preview.png"},"is_featured":{"type":"boolean","example":false},"utm_source":{"type":"string","nullable":true,"maxLength":255,"example":"newsletter"},"utm_medium":{"type":"string","nullable":true,"maxLength":255,"example":"email"},"utm_campaign":{"type":"string","nullable":true,"maxLength":255,"example":"autumn-launch"},"utm_term":{"type":"string","nullable":true,"maxLength":255,"example":"short links"},"utm_content":{"type":"string","nullable":true,"maxLength":255,"example":"hero-cta"}}},"LinkStatsResponse":{"type":"object","properties":{"total_clicks":{"type":"integer","example":1250},"lifetime_clicks":{"type":"integer","example":4870},"last_clicked_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-15T10:00:00.000Z"},"unique_visitors_available":{"type":"boolean","description":"False when the window carries no unique-visitor data. Nothing collects unique visitors per link yet, so `unique_visitors` is omitted and must be shown as \"not collected\", not as zero.","example":false},"breakdowns_available":{"type":"boolean","description":"False when the window carries no country/device/referrer rows. Nothing collects those per link yet, so `referrers` / `countries` / `devices` are omitted and must be shown as \"not collected\".","example":false},"unique_visitors":{"type":"integer","description":"Omitted until per-link tracking for this metric exists.","example":980},"timeseries":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time","example":"2026-01-15T00:00:00Z"},"clicks":{"type":"integer","example":45},"unique_visitors":{"type":"integer","example":38}},"required":["timestamp","clicks","unique_visitors"]}},"referrers":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","example":"twitter.com"},"clicks":{"type":"integer","example":320}},"required":["domain","clicks"]},"description":"Omitted until per-link tracking for this metric exists."},"countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","example":"US"},"clicks":{"type":"integer","example":510}},"required":["country","clicks"]},"description":"Omitted until per-link tracking for this metric exists."},"devices":{"type":"array","items":{"type":"object","properties":{"device_type":{"type":"string","example":"mobile"},"clicks":{"type":"integer","example":680}},"required":["device_type","clicks"]},"description":"Omitted until per-link tracking for this metric exists."}},"required":["total_clicks","lifetime_clicks","last_clicked_at","unique_visitors_available","breakdowns_available","timeseries"]},"GenerateMetadataResponse":{"type":"object","properties":{"title":{"type":"string","example":"OrphLink — branded link shortener"},"description":{"type":"string","example":"A multi-tenant link shortener and proxy platform with branded redirect pages."},"ogText":{"type":"string","example":"Shorten, brand and track your links with OrphLink."}},"required":["title","description","ogText"]},"OgImageResponse":{"type":"object","properties":{"og_image_url":{"type":"string","format":"uri","example":"https://orphl.ink/r2/og/ws_abc/lnk_abc-1a2b3c4d.png"},"og_image_key":{"type":"string","example":"og/ws_abc/lnk_abc-1a2b3c4d.png"}},"required":["og_image_url","og_image_key"]},"TagListResponse":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"},"link_count":{"type":"integer","example":15}},"required":["id","name","color","link_count"]}}},"required":["tags"]},"TagResponse":{"type":"object","properties":{"tag":{"type":"object","properties":{"id":{"type":"string","example":"tag_abc123"},"name":{"type":"string","example":"marketing"},"color":{"type":"string","nullable":true,"example":"#ef4444"}},"required":["id","name","color"]}},"required":["tag"]},"CreateTagInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":50,"example":"marketing"},"color":{"type":"string","maxLength":20,"example":"#ef4444"}},"required":["name"]},"UpdateTagInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":50,"example":"marketing-v2"},"color":{"type":"string","maxLength":20,"example":"#3b82f6"}}},"GroupListResponse":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"},"description":{"type":"string","nullable":true,"example":"All links for Q1 campaign"},"link_count":{"type":"integer","example":8},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","name","description","link_count","created_at"]}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["groups","meta"]},"GroupResponse":{"type":"object","properties":{"group":{"type":"object","properties":{"id":{"type":"string","example":"grp_abc123"},"name":{"type":"string","example":"Campaign Q1"},"description":{"type":"string","nullable":true,"example":"All links for Q1 campaign"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:30:00Z"}},"required":["id","name","description","created_at"]}},"required":["group"]},"CreateGroupInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Campaign Q1"},"description":{"type":"string","maxLength":500,"example":"All links for Q1 campaign"}},"required":["name"]},"UpdateGroupInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Campaign Q1 Updated"},"description":{"type":"string","maxLength":500,"example":"Updated description"}}},"WebhookListResponse":{"type":"object","properties":{"webhooks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"wh_abc123"},"name":{"type":"string","example":"Analytics Webhook"},"url":{"type":"string","format":"uri","example":"https://example.com/webhooks/orphlink"},"events":{"type":"array","items":{"type":"string","enum":["link.moderation_complete","link.health_failed","conversion.recorded","link.created","link.clicked","link.expired","link.threshold_reached"],"description":"A subscribed event. link.created, link.clicked, link.expired, link.threshold_reached are retired: kept on existing endpoints but never delivered and not accepted for new subscriptions."},"example":["link.moderation_complete","link.health_failed"]},"active":{"type":"boolean","example":true},"click_threshold":{"type":"integer","nullable":true,"example":1000}},"required":["id","name","url","events","active","click_threshold"]}}},"required":["webhooks"]},"WebhookResponse":{"type":"object","properties":{"webhook":{"type":"object","properties":{"id":{"type":"string","example":"wh_abc123"},"name":{"type":"string","example":"Analytics Webhook"},"url":{"type":"string","format":"uri","example":"https://example.com/webhooks/orphlink"},"events":{"type":"array","items":{"type":"string","enum":["link.moderation_complete","link.health_failed","conversion.recorded","link.created","link.clicked","link.expired","link.threshold_reached"],"description":"A subscribed event. link.created, link.clicked, link.expired, link.threshold_reached are retired: kept on existing endpoints but never delivered and not accepted for new subscriptions."},"example":["link.moderation_complete","link.health_failed"]},"active":{"type":"boolean","example":true},"click_threshold":{"type":"integer","nullable":true,"example":1000}},"required":["id","name","url","events","active","click_threshold"]},"secret":{"type":"string","example":"whsec_verylongsecretkey123"}},"required":["webhook"]},"CreateWebhookInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Analytics Webhook"},"url":{"type":"string","format":"uri","example":"https://example.com/webhooks/orphlink"},"events":{"type":"array","items":{"type":"string","enum":["link.moderation_complete","link.health_failed","conversion.recorded"],"description":"Subscribable webhook event. link.moderation_complete: a moderation verdict was applied to the link destination. link.health_failed: a link health check turned the link broken (sent once per transition). conversion.recorded: a conversion was recorded."},"minItems":1,"example":["link.moderation_complete","link.health_failed"]},"click_threshold":{"type":"integer","minimum":1,"example":1000},"secret":{"type":"string","minLength":16,"example":"whsec_verylongsecretkey123"}},"required":["name","url","events"]},"UpdateWebhookInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Updated Webhook"},"url":{"type":"string","format":"uri","example":"https://example.com/webhooks/v2"},"events":{"type":"array","items":{"type":"string","enum":["link.moderation_complete","link.health_failed","conversion.recorded","link.created","link.clicked","link.expired","link.threshold_reached"],"description":"A subscribed event. link.created, link.clicked, link.expired, link.threshold_reached are retired: kept on existing endpoints but never delivered and not accepted for new subscriptions."},"minItems":1,"description":"Replaces the subscribed events. A retired event is accepted only if the endpoint already lists it, so a stored subscription can be saved back unchanged; retired events can be removed but not added (400 RETIRED_WEBHOOK_EVENT)."},"click_threshold":{"type":"integer","nullable":true,"minimum":1},"active":{"type":"boolean"}}},"DeliveryListResponse":{"type":"object","properties":{"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"dlv_abc123"},"event":{"type":"string","enum":["link.moderation_complete","link.health_failed","conversion.recorded","webhook.test"],"description":"Event a delivery carried. webhook.test is the ping sent by POST /webhooks/{webhookId}/test.","example":"link.health_failed"},"status_code":{"type":"integer","nullable":true,"example":200},"success":{"type":"boolean","example":true},"attempt_count":{"type":"integer","example":1},"payload":{"type":"object","additionalProperties":{"nullable":true},"example":{}},"response":{"type":"string","nullable":true,"example":"OK"},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:31:00Z"}},"required":["id","event","status_code","success","attempt_count","payload","response","created_at"]}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["deliveries","meta"]},"WebhookTestQueuedResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"queued":{"type":"boolean","enum":[true]},"delivery_id":{"type":"string","example":"dlv_abc123"},"status":{"type":"string","enum":["pending"]}},"required":["success","queued","delivery_id","status"]},"ApiKeyListResponse":{"type":"object","properties":{"api_keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"key_abc123"},"name":{"type":"string","example":"CI/CD Pipeline Key"},"key_prefix":{"type":"string","example":"olk_a1b2"},"scopes":{"type":"array","items":{"type":"string"},"example":["links:read","links:write"]},"last_used_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-03-20T14:00:00Z"},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2027-01-01T00:00:00Z"}},"required":["id","name","key_prefix","scopes","last_used_at","expires_at"]}}},"required":["api_keys"]},"ApiKeyResponse":{"type":"object","properties":{"api_key":{"type":"object","properties":{"id":{"type":"string","example":"key_abc123"},"name":{"type":"string","example":"CI/CD Pipeline Key"},"key_prefix":{"type":"string","example":"olk_a1b2"},"scopes":{"type":"array","items":{"type":"string"},"example":["links:read","links:write"]},"last_used_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-03-20T14:00:00Z"},"expires_at":{"type":"string","nullable":true,"format":"date-time","example":"2027-01-01T00:00:00Z"}},"required":["id","name","key_prefix","scopes","last_used_at","expires_at"]},"secret":{"type":"string","example":"olk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"}},"required":["api_key"]},"CreateApiKeyInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"CI/CD Pipeline Key"},"scopes":{"type":"array","items":{"type":"string","enum":["links:read","links:write","analytics:read","workspaces:read","workspaces:write","conversions:write"]},"example":["links:read","links:write","analytics:read"]},"expires_at":{"type":"string","format":"date-time","example":"2027-01-01T00:00:00Z"}},"required":["name"]},"ProxySessionListResponse":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string","example":"my-link"},"active_viewers":{"type":"integer","example":3},"avg_session_duration":{"type":"number","example":45.2},"started_at":{"type":"string","format":"date-time","example":"2026-01-15T10:00:00Z"}},"required":["slug","active_viewers","avg_session_duration","started_at"]}}},"required":["sessions"]},"ProxySessionDetailResponse":{"type":"object","properties":{"slug":{"type":"string","example":"my-link"},"active_viewers":{"type":"integer","example":3},"viewers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"sess_abc123"},"started_at":{"type":"string","format":"date-time","example":"2026-01-15T10:00:00Z"},"page_loads":{"type":"integer","example":5},"duration":{"type":"integer","example":45}},"required":["id","started_at","page_loads","duration"]}},"metrics":{"type":"object","properties":{"total_viewers":{"type":"integer","example":12},"avg_session_duration":{"type":"number","example":45.2}},"required":["total_viewers","avg_session_duration"]}},"required":["slug","active_viewers","viewers","metrics"]},"AnalyticsSummaryResponse":{"type":"object","properties":{"total_clicks":{"type":"integer","example":15420},"unique_visitors":{"type":"integer","example":8230},"total_links":{"type":"integer","example":245},"total_active_links":{"type":"integer","example":198},"active_proxy_sessions":{"type":"integer","example":0},"total_conversions":{"type":"integer","example":312},"conversion_rate":{"type":"number","example":0.0202},"unique_visitors_available":{"type":"boolean","description":"False when the period carries no unique-visitor data. The click pipeline does not collect unique visitors yet, so `unique_visitors` is 0 and must be shown as \"not collected\", not as zero visitors.","example":false},"breakdowns_available":{"type":"boolean","description":"False when the period carries no country/device/referrer rows. The click pipeline does not collect these breakdowns yet, so `top_referrers` / `top_countries` are empty and must be shown as \"not collected\".","example":false},"top_links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"lnk_abc123"},"slug":{"type":"string","example":"my-link"},"short_url":{"type":"string","format":"uri","example":"https://orphl.ink/my-link"},"click_count":{"type":"integer","example":1250}},"required":["id","slug","short_url","click_count"]}},"top_referrers":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","example":"twitter.com"},"clicks":{"type":"integer","example":3200}},"required":["domain","clicks"]}},"top_countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","example":"US"},"clicks":{"type":"integer","example":5100}},"required":["country","clicks"]}}},"required":["total_clicks","unique_visitors","total_links","total_active_links","active_proxy_sessions","total_conversions","conversion_rate","unique_visitors_available","breakdowns_available","top_links","top_referrers","top_countries"]},"TimeseriesResponse":{"type":"object","properties":{"granularity":{"type":"string","enum":["hour","day","week","month"],"description":"Granularity applied. `minute` is served as `hour`: rollups are hourly.","example":"day"},"truncated":{"type":"boolean","description":"True when only the most recent 2200 buckets were returned.","example":false},"unique_visitors_available":{"type":"boolean","description":"False when no bucket carries unique-visitor data (not collected yet); show \"not collected\".","example":false},"timeseries":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time","example":"2026-01-15T00:00:00Z"},"clicks":{"type":"integer","example":120},"unique_visitors":{"type":"integer","example":95}},"required":["timestamp","clicks","unique_visitors"]}}},"required":["granularity","truncated","unique_visitors_available","timeseries"]},"VerifyPasswordInput":{"type":"object","properties":{"password":{"type":"string","minLength":1,"example":"secretpass"}},"required":["password"]}},"parameters":{}},"paths":{"/health":{"get":{"tags":["System"],"summary":"Liveness check - always returns 200","responses":{"200":{"description":"Service is alive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ready":{"get":{"tags":["System"],"summary":"Readiness check - verifies all bindings are available","responses":{"200":{"description":"Service is ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadyResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadyResponse"}}}}}}},"/auth/register":{"post":{"tags":["Auth"],"summary":"Register a new user","description":"Create a new user account with email/password, provision a default workspace, and return tokens.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterInput"}}}},"responses":{"201":{"description":"User registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Registration disabled (REGISTRATION_DISABLED) or Turnstile verification failed (TURNSTILE_FAILED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Email already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/login":{"post":{"tags":["Auth"],"summary":"Log in with email and password","description":"Authenticate with email/password and receive JWT access token, refresh token, and workspace list.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginInput"}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Turnstile verification failed (TURNSTILE_FAILED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many failed sign-ins for this email (same response whether or not the account exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/refresh":{"post":{"tags":["Auth"],"summary":"Refresh access token","description":"Exchange a valid refresh token for a new access token and refresh token pair (rotation).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshInput"}}}},"responses":{"200":{"description":"Tokens refreshed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenPairResponse"}}}},"401":{"description":"Invalid refresh token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/logout":{"post":{"tags":["Auth"],"summary":"Log out and revoke refresh token","description":"Revoke the provided refresh token. Possession of the refresh token is the credential — no access token is required (API keys are refused). Always 200, whether or not the token was live.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutInput"}}}},"responses":{"200":{"description":"Logged out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"API keys cannot log out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/me":{"get":{"tags":["Auth"],"summary":"Get current user profile","description":"Return the authenticated user profile and their workspace memberships.","responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Auth"],"summary":"Update the current user profile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileInput"}}}},"responses":{"200":{"description":"Updated profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/change-password":{"post":{"tags":["Auth"],"summary":"Change the current user password","description":"Verifies the current password, sets a new one, and revokes all refresh tokens so other sessions sign out.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordInput"}}}},"responses":{"200":{"description":"Password changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Current password incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/select-workspace":{"post":{"tags":["Auth"],"summary":"Switch the active workspace, issuing fresh tokens","description":"Validates the supplied auth_token, confirms workspace membership, and returns a new access/refresh pair scoped to the chosen workspace.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectWorkspaceInput"}}}},"responses":{"200":{"description":"Workspace selected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"401":{"description":"Invalid auth token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a workspace member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password reset email","description":"Always returns success — does not disclose whether the email is registered.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordInput"}}}},"responses":{"200":{"description":"Reset email queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Turnstile verification failed (TURNSTILE_FAILED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many reset requests for this email (same response whether or not the account exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Complete a password reset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordInput"}}}},"responses":{"200":{"description":"Password updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/waitlist":{"post":{"tags":["Waitlist"],"summary":"Join the waitlist","description":"Registration is invite-only during early access. Leave an email to be notified when registration opens. Idempotent — re-submitting the same email succeeds without creating a duplicate.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistInput"}}}},"responses":{"201":{"description":"On the list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Turnstile verification failed (TURNSTILE_FAILED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces":{"get":{"tags":["Workspaces"],"summary":"List user workspaces","description":"Return all workspaces the authenticated user is a member of.","responses":{"200":{"description":"Workspace list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Workspaces"],"summary":"Create a workspace","description":"Create a new workspace. The authenticated user becomes the owner.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkspaceInput"}}}},"responses":{"201":{"description":"Workspace created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}":{"get":{"tags":["Workspaces"],"summary":"Get workspace details","description":"Return details for a specific workspace. Requires workspace membership.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Workspace details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Workspaces"],"summary":"Update workspace settings","description":"Update workspace name, description, or settings. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkspaceInput"}}}},"responses":{"200":{"description":"Workspace updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Workspaces"],"summary":"Delete a workspace","description":"Soft-delete a workspace. Cannot delete the default workspace. Requires owner role.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Workspace deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Cannot delete default","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/members":{"get":{"tags":["Workspaces"],"summary":"List workspace members","description":"Return all members of a workspace with their roles.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Member list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Workspaces"],"summary":"Add a member to workspace","description":"Invite a user to the workspace by email. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMemberInput"}}}},"responses":{"201":{"description":"Member added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/members/{userId}":{"patch":{"tags":["Workspaces"],"summary":"Update member role","description":"Change a workspace member role. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"usr_abc123"},"required":true,"name":"userId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberInput"}}}},"responses":{"200":{"description":"Role updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Workspaces"],"summary":"Remove a member from workspace","description":"Remove a user from the workspace. Cannot remove the last owner. Requires owner role.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"usr_abc123"},"required":true,"name":"userId","in":"path"}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Cannot remove last owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding":{"get":{"tags":["Branding"],"summary":"Get workspace branding","description":"Return branding settings for a workspace including colours, tagline, and asset URLs.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Branding settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Branding"],"summary":"Update workspace branding","description":"Update branding colours, tagline, interstitial message, and custom CSS. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBrandingInput"}}}},"responses":{"200":{"description":"Branding updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding/logo":{"post":{"tags":["Branding"],"summary":"Upload workspace logo","description":"Upload a logo image for the workspace. Accepts PNG, JPEG, SVG, WebP, GIF. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"logo":{"type":"string","format":"binary"}},"required":["logo"]}}}},"responses":{"200":{"description":"Logo uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAssetResponse"}}}},"400":{"description":"Invalid file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"File too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding/favicon":{"post":{"tags":["Branding"],"summary":"Upload workspace favicon","description":"Upload a favicon for the workspace. Accepts PNG, ICO, SVG. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"favicon":{"type":"string","format":"binary"}},"required":["favicon"]}}}},"responses":{"200":{"description":"Favicon uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAssetResponse"}}}},"400":{"description":"Invalid file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"File too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding/presets":{"get":{"tags":["Branding"],"summary":"List theme presets","description":"Return built-in theme presets plus any presets saved for this workspace.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Preset list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThemePresetListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Branding"],"summary":"Save a theme preset","description":"Save the supplied colour set as a named preset. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateThemePresetInput"}}}},"responses":{"201":{"description":"Preset saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThemePresetResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Preset name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding/presets/{presetId}":{"patch":{"tags":["Branding"],"summary":"Update a theme preset","description":"Update a saved preset. Built-in presets cannot be edited. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"tp_abc123"},"required":true,"name":"presetId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateThemePresetInput"}}}},"responses":{"200":{"description":"Preset updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThemePresetResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Preset name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Branding"],"summary":"Delete a theme preset","description":"Delete a saved preset. Built-in presets cannot be deleted. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"tp_abc123"},"required":true,"name":"presetId","in":"path"}],"responses":{"200":{"description":"Preset deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/branding/presets/{presetId}/apply":{"post":{"tags":["Branding"],"summary":"Apply a theme preset","description":"Copy the preset colours onto the workspace branding (one-click apply). Works for built-in and saved presets. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"tp_abc123"},"required":true,"name":"presetId","in":"path"}],"responses":{"200":{"description":"Preset applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyThemePresetResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/utm-templates":{"get":{"tags":["UTM Templates"],"summary":"List UTM templates","description":"Return all saved UTM/campaign presets for the workspace.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Template list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmTemplateListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["UTM Templates"],"summary":"Create a UTM template","description":"Save a reusable set of UTM parameters under a name. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUtmTemplateInput"}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmTemplateResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Template name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/utm-templates/{templateId}":{"patch":{"tags":["UTM Templates"],"summary":"Update a UTM template","description":"Update a saved UTM template. A field set to null clears it. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"utm_abc123"},"required":true,"name":"templateId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUtmTemplateInput"}}}},"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmTemplateResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Template name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["UTM Templates"],"summary":"Delete a UTM template","description":"Delete a saved UTM template. Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string","example":"utm_abc123"},"required":true,"name":"templateId","in":"path"}],"responses":{"200":{"description":"Template deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/conversions":{"post":{"tags":["Conversions"],"summary":"Record a conversion for a link (server-side postback)","description":"Idempotently attribute a conversion to a prior click. Pass the `clickId` (the `olc` param from the redirect). A duplicate postback for the same clickId is a no-op and returns the existing conversion with `created: false`. Requires an API key with the `conversions:write` scope.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordConversionInput"}}}},"responses":{"200":{"description":"Conversion already recorded (idempotent replay)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"}}}},"201":{"description":"Conversion recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden / missing scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CLICK_ID_CONFLICT: the clickId is already attributed to another link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/domains":{"get":{"tags":["Custom Domains"],"summary":"List custom domains","description":"Return all custom domains for the workspace plus whether the feature is enabled on this deployment.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"responses":{"200":{"description":"Domain list","content":{"application/json":{"schema":{"type":"object","properties":{"domains":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","enum":["pending_verification","verifying","active","failed","deleting"]},"sslStatus":{"type":"string","nullable":true},"ownershipStatus":{"type":"string","nullable":true},"verificationRecords":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["txt","cname","http"]},"name":{"type":"string"},"value":{"type":"string"},"purpose":{"type":"string","enum":["ssl_validation","ownership","hostname"]}},"required":["type","name","value","purpose"]}},"lastError":{"type":"string","nullable":true},"verifiedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","workspaceId","hostname","status","sslStatus","ownershipStatus","verificationRecords","lastError","verifiedAt","createdAt"]}},"featureEnabled":{"type":"boolean"}},"required":["domains","featureEnabled"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Custom Domains"],"summary":"Add a custom domain","description":"Register a custom hostname for the workspace via Cloudflare for SaaS. Returns the domain with the DNS records the customer must add to verify ownership and provision TLS. Requires admin or owner. Requires the feature to be enabled on the deployment.","parameters":[{"schema":{"type":"string","example":"ws_abc123"},"required":true,"name":"workspaceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"hostname":{"type":"string","minLength":1,"maxLength":253,"example":"go.acme.com"}},"required":["hostname"]}}}},"responses":{"201":{"description":"Domain added","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","enum":["pending_verification","verifying","active","failed","deleting"]},"sslStatus":{"type":"string","nullable":true},"ownershipStatus":{"type":"string","nullable":true},"verificationRecords":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["txt","cname","http"]},"name":{"type":"string"},"value":{"type":"string"},"purpose":{"type":"string","enum":["ssl_validation","ownership","hostname"]}},"required":["type","name","value","purpose"]}},"lastError":{"type":"string","nullable":true},"verifiedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","workspaceId","hostname","status","sslStatus","ownershipStatus","verificationRecords","lastError","verifiedAt","createdAt"]}},"required":["domain"]}}}},"400":{"description":"Invalid hostname","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden / feature disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Hostname already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/domains/{domainId}/verify":{"post":{"tags":["Custom Domains"],"summary":"Re-check domain verification status","description":"Poll Cloudflare for the current SSL / ownership state of the custom hostname and persist any change. Returns the updated domain. Requires admin or owner.","parameters":[{"schema":{"type":"string"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string"},"required":true,"name":"domainId","in":"path"}],"responses":{"200":{"description":"Updated domain","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","enum":["pending_verification","verifying","active","failed","deleting"]},"sslStatus":{"type":"string","nullable":true},"ownershipStatus":{"type":"string","nullable":true},"verificationRecords":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["txt","cname","http"]},"name":{"type":"string"},"value":{"type":"string"},"purpose":{"type":"string","enum":["ssl_validation","ownership","hostname"]}},"required":["type","name","value","purpose"]}},"lastError":{"type":"string","nullable":true},"verifiedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","workspaceId","hostname","status","sslStatus","ownershipStatus","verificationRecords","lastError","verifiedAt","createdAt"]}},"required":["domain"]}}}},"403":{"description":"Forbidden / feature disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/domains/{domainId}":{"delete":{"tags":["Custom Domains"],"summary":"Remove a custom domain","description":"Delete the custom hostname from Cloudflare (best-effort) and remove it from the workspace. Requires admin or owner.","parameters":[{"schema":{"type":"string"},"required":true,"name":"workspaceId","in":"path"},{"schema":{"type":"string"},"required":true,"name":"domainId","in":"path"}],"responses":{"200":{"description":"Domain removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/search":{"get":{"tags":["Links"],"summary":"Semantic search across links","description":"Search workspace links by meaning. Embeds the query and ANN-searches the per-workspace Vectorize index; falls back to SQL keyword (title/slug/destination_url LIKE) search when Vectorize is unavailable or yields no hits. `mode` reports which path produced the results.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":512,"example":"that pricing page I shared last month"},"required":true,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SemanticSearchResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/similar":{"get":{"tags":["Links"],"summary":"Find near-duplicate links","description":"Return existing workspace links that are semantic near-duplicates of the given link (same article via different URLs, tracking-param variants, mirrors). Powers the \"you already shortened this\" nudge. Requires Vectorize; returns an empty set with vectorize_enabled=false otherwise.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":10},"required":false,"name":"limit","in":"query"},{"schema":{"type":"number","nullable":true,"minimum":0,"maximum":1,"example":0.92},"required":false,"name":"threshold","in":"query"}],"responses":{"200":{"description":"Similar links","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimilarLinksResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/backfill-embeddings":{"post":{"tags":["Links"],"summary":"Backfill link embeddings","description":"Admin only. Enqueue active workspace links for embedding so duplicate detection and semantic search cover links created before Vectorize was wired. Capped per call (≤500) — re-call to drain a large backlog. No-op when Vectorize is unavailable.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillEmbeddingsInput"}}}},"responses":{"200":{"description":"Backfill enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackfillEmbeddingsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/bulk":{"post":{"tags":["Links"],"summary":"Bulk create links","description":"Create multiple links at once (up to 100). Slugs are auto-generated. Items are created independently: 201 when every item was created, 207 when any item failed, listing the created links and each failed item (by its index in the request) so a retry can resend only the failures.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreateInput"}}}},"responses":{"201":{"description":"All links created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreateResponse"}}}},"207":{"description":"Some items failed; see errors (created links are listed in links)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreateResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Links"],"summary":"Bulk delete links","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"link_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500}},"required":["link_ids"]}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"processed":{"type":"integer"},"skipped":{"type":"array","items":{"type":"string"}}},"required":["processed","skipped"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/bulk/tags":{"post":{"tags":["Links"],"summary":"Replace tag set for many links","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"link_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500},"tag_ids":{"type":"array","items":{"type":"string"}}},"required":["link_ids","tag_ids"]}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"processed":{"type":"integer"},"skipped":{"type":"array","items":{"type":"string"}}},"required":["processed","skipped"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A tag is not in this workspace (TAG_NOT_FOUND)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/bulk/group":{"post":{"tags":["Links"],"summary":"Set group on many links (pass group_id: null to clear)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"link_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500},"group_id":{"type":"string","nullable":true}},"required":["link_ids","group_id"]}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"processed":{"type":"integer"},"skipped":{"type":"array","items":{"type":"string"}}},"required":["processed","skipped"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The group is not in this workspace (GROUP_NOT_FOUND)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/export":{"get":{"tags":["Links"],"summary":"Export links","description":"Export workspace links as CSV or JSON. Sets Content-Disposition for download. Members export only the links they created; admins and owners export every link in the workspace.","parameters":[{"schema":{"type":"string","enum":["csv","json"],"default":"csv"},"required":false,"name":"format","in":"query"},{"schema":{"type":"string"},"required":false,"name":"group_id","in":"query"},{"schema":{"type":"string","enum":["true","false","1","0"],"example":"true"},"required":false,"name":"is_active","in":"query"}],"responses":{"200":{"description":"File download"},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/import":{"post":{"tags":["Links"],"summary":"Import links from CSV","description":"Upload a CSV file with destination_url column. Max 500 rows. Rows are created independently: 201 when every row was created, 207 when any row failed, listing created and failed rows by CSV line number.","responses":{"201":{"description":"All rows imported","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportLinksResponse"}}}},"207":{"description":"Some rows failed; see errors (created rows are listed in links)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportLinksResponse"}}}},"400":{"description":"No file, empty CSV (IMPORT_EMPTY) or more than 500 rows (IMPORT_TOO_MANY_ROWS)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"CSV exceeds 2 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links":{"get":{"tags":["Links"],"summary":"List links","description":"Return paginated links for the current workspace with optional filters.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"per_page","in":"query"},{"schema":{"type":"string","example":"marketing"},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","example":"tag_abc123"},"required":false,"name":"tag_id","in":"query"},{"schema":{"type":"string","example":"grp_abc123"},"required":false,"name":"group_id","in":"query"},{"schema":{"type":"string","enum":["direct","interstitial","proxy"]},"required":false,"name":"redirect_mode","in":"query"},{"schema":{"type":"string","enum":["pending_moderation","approved","rejected","review_needed"]},"required":false,"name":"moderation_status","in":"query"},{"schema":{"type":"string","enum":["true","false","1","0"],"example":"true"},"required":false,"name":"is_active","in":"query"},{"schema":{"type":"string","enum":["active","expired","scheduled","pending_moderation","rejected","password_protected"],"description":"Matches the link `status` field. `expired` includes deactivated links; `active` includes password-protected links."},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["healthy","broken","unknown"]},"required":false,"name":"health_status","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","enum":["true","false","1","0"],"example":"true"},"required":false,"name":"featured","in":"query"},{"schema":{"type":"string","enum":["created_at","click_count","title","slug"],"default":"created_at"},"required":false,"name":"sort_by","in":"query"},{"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"required":false,"name":"sort_order","in":"query"}],"responses":{"200":{"description":"Link list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkListResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Links"],"summary":"Create a link","description":"Create a new shortened link. Supports custom slugs, password protection, and expiry.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkInput"}}}},"responses":{"201":{"description":"Link created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkResponse"}}}},"400":{"description":"Invalid body, INVALID_SLUG or SLUG_RESERVED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"GROUP_NOT_FOUND or TAG_NOT_FOUND in this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already in use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"UNSAFE_DESTINATION_URL or UNSAFE_OG_IMAGE_URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}":{"get":{"tags":["Links"],"summary":"Get link details","description":"Return full details for a specific link including tags and group.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Link details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Links"],"summary":"Update a link","description":"Update link properties. Changing destination_url triggers re-moderation.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkInput"}}}},"responses":{"200":{"description":"Link updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"LINK_NOT_FOUND, or GROUP_NOT_FOUND / TAG_NOT_FOUND in this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"UNSAFE_DESTINATION_URL or UNSAFE_OG_IMAGE_URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Links"],"summary":"Delete a link","description":"Soft-delete a link and invalidate its KV cache.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Link deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/variants":{"get":{"tags":["Links"],"summary":"Get link variants","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Variants","content":{"application/json":{"schema":{"type":"object","properties":{"variants":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"destination_url":{"type":"string","format":"uri"},"weight":{"type":"integer"},"label":{"type":"string","nullable":true},"click_count":{"type":"integer"},"rotation_mode":{"type":"string","enum":["weighted","sequential","round_robin"]}},"required":["id","destination_url","weight","label","click_count","rotation_mode"]}}},"required":["variants"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Links"],"summary":"Replace all link variants","description":"Atomically replaces all variants. Weights must sum to 100. Empty array reverts to single-destination.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"variants":{"type":"array","items":{"type":"object","properties":{"destination_url":{"type":"string","format":"uri"},"weight":{"type":"integer","minimum":1,"maximum":100,"default":50},"label":{"type":"string","nullable":true,"maxLength":100}},"required":["destination_url"]}},"rotation_mode":{"type":"string","enum":["weighted","sequential","round_robin"]}},"required":["variants"]}}}},"responses":{"200":{"description":"Variants replaced","content":{"application/json":{"schema":{"type":"object","properties":{"variants":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"destination_url":{"type":"string","format":"uri"},"weight":{"type":"integer"},"label":{"type":"string","nullable":true},"click_count":{"type":"integer"},"rotation_mode":{"type":"string","enum":["weighted","sequential","round_robin"]}},"required":["id","destination_url","weight","label","click_count","rotation_mode"]}}},"required":["variants"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A destination or store URL is not allowed (UNSAFE_DESTINATION_URL)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/device-targets":{"get":{"tags":["Links"],"summary":"Get device targets for a link","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Device targets","content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"device_type":{"type":"string"},"destination_url":{"type":"string","format":"uri"},"app_scheme_url":{"type":"string","nullable":true},"ios_store_url":{"type":"string","nullable":true,"format":"uri"},"android_store_url":{"type":"string","nullable":true,"format":"uri"}},"required":["id","device_type","destination_url","app_scheme_url","ios_store_url","android_store_url"]}}},"required":["targets"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Links"],"summary":"Replace device targets for a link","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"device_type":{"type":"string","enum":["mobile_ios","mobile_android","desktop","fallback"]},"destination_url":{"type":"string","format":"uri"},"app_scheme_url":{"type":"string","nullable":true,"maxLength":2048,"example":"myapp://product/123"},"ios_store_url":{"type":"string","nullable":true,"format":"uri","example":"https://apps.apple.com/app/id000000000"},"android_store_url":{"type":"string","nullable":true,"format":"uri","example":"https://play.google.com/store/apps/details?id=com.example"}},"required":["device_type","destination_url"]}}},"required":["targets"]}}}},"responses":{"200":{"description":"Targets replaced","content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"device_type":{"type":"string"},"destination_url":{"type":"string","format":"uri"},"app_scheme_url":{"type":"string","nullable":true},"ios_store_url":{"type":"string","nullable":true,"format":"uri"},"android_store_url":{"type":"string","nullable":true,"format":"uri"}},"required":["id","device_type","destination_url","app_scheme_url","ios_store_url","android_store_url"]}}},"required":["targets"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A destination or store URL is not allowed (UNSAFE_DESTINATION_URL)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/geo-targets":{"get":{"tags":["Links"],"summary":"Get geo targets for a link","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Geo targets","content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"country_codes":{"type":"string"},"destination_url":{"type":"string","format":"uri"}},"required":["id","country_codes","destination_url"]}}},"required":["targets"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Links"],"summary":"Replace geo targets for a link","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"country_codes":{"type":"string","minLength":2},"destination_url":{"type":"string","format":"uri"}},"required":["country_codes","destination_url"]}}},"required":["targets"]}}}},"responses":{"200":{"description":"Targets replaced","content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"country_codes":{"type":"string"},"destination_url":{"type":"string","format":"uri"}},"required":["id","country_codes","destination_url"]}}},"required":["targets"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A destination or store URL is not allowed (UNSAFE_DESTINATION_URL)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/stats":{"get":{"tags":["Links"],"summary":"Get link statistics","description":"Return click statistics and analytics data for a specific link.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"7d","example":"7d"},"required":false,"name":"period","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"start","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"end","in":"query"}],"responses":{"200":{"description":"Link statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkStatsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/moderate":{"post":{"tags":["Links"],"summary":"Request re-moderation","description":"Enqueue an immediate moderation re-check for a link. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Re-moderation queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/generate-metadata":{"post":{"tags":["Links"],"summary":"Regenerate AI link metadata","description":"Fetch the destination fresh and regenerate the link title + description (and OG/social preview text) via Workers AI. Overwrites existing title/description. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Metadata regenerated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateMetadataResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Could not generate metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/health/check":{"post":{"tags":["Links"],"summary":"Run immediate health check","description":"Runs a HEAD/GET health check on the link destination and records the result.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Health check result","content":{"application/json":{"schema":{"type":"object","properties":{"health_status":{"type":"string","enum":["healthy","broken"]},"status_code":{"type":"integer","nullable":true},"response_time_ms":{"type":"integer","nullable":true}},"required":["health_status","status_code","response_time_ms"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/health":{"get":{"tags":["Links"],"summary":"Get link health probe history","description":"Returns up to N most recent health probes for the link (default 100, max 500).","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Health history","content":{"application/json":{"schema":{"type":"object","properties":{"health_status":{"type":"string","enum":["healthy","broken","unknown"]},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"http_status":{"type":"integer","nullable":true},"response_time_ms":{"type":"integer","nullable":true},"error":{"type":"string","nullable":true},"checked_at":{"type":"string"}},"required":["id","http_status","response_time_ms","error","checked_at"]}}},"required":["health_status","checks"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/og-image":{"post":{"tags":["Links"],"summary":"Generate branded OG preview image","description":"Render a branded Open Graph preview card (title + workspace branding) for the link via Browser Rendering, cache it in R2, and return its public URL. The card becomes the link's og:image on social unfurls. Requires admin or owner role. Returns 409 if the link has a manual og_image_url override (clear it first), or 422 if OG image generation is not enabled for this deployment.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"OG image generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OgImageResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Manual override present","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Disabled or generation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/links/{linkId}/qr":{"get":{"tags":["Links"],"summary":"Generate QR code for link","description":"Generate a QR code image encoding the short URL. Supports SVG (default) and PNG formats.","parameters":[{"schema":{"type":"string","example":"lnk_abc123"},"required":true,"name":"linkId","in":"path"},{"schema":{"type":"string","enum":["svg","png"],"default":"svg"},"required":false,"name":"format","in":"query"},{"schema":{"type":"integer","minimum":100,"maximum":2000,"default":300},"required":false,"name":"size","in":"query"},{"schema":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#000000"},"required":false,"name":"fg_colour","in":"query"},{"schema":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$","example":"#ffffff"},"required":false,"name":"bg_colour","in":"query"}],"responses":{"200":{"description":"QR code image","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tags":{"get":{"tags":["Tags"],"summary":"List tags","description":"Return all tags for the current workspace with link counts.","responses":{"200":{"description":"Tag list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tags"],"summary":"Create a tag","description":"Create a new tag within the current workspace. Tag names must be unique per workspace.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTagInput"}}}},"responses":{"201":{"description":"Tag created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Tag name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tags/{tagId}":{"patch":{"tags":["Tags"],"summary":"Update a tag","description":"Update tag name or colour. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"tag_abc123"},"required":true,"name":"tagId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTagInput"}}}},"responses":{"200":{"description":"Tag updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Tag name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Tags"],"summary":"Delete a tag","description":"Delete a tag and remove all link-tag associations. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"tag_abc123"},"required":true,"name":"tagId","in":"path"}],"responses":{"200":{"description":"Tag deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/groups":{"get":{"tags":["Groups"],"summary":"List groups","description":"Return all link groups for the current workspace with link counts.","responses":{"200":{"description":"Group list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Groups"],"summary":"Create a group","description":"Create a new link group within the current workspace. Group names must be unique per workspace.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupInput"}}}},"responses":{"201":{"description":"Group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Group name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/groups/{groupId}":{"patch":{"tags":["Groups"],"summary":"Update a group","description":"Update group name or description. Requires admin or owner role.","parameters":[{"schema":{"type":"string","example":"grp_abc123"},"required":true,"name":"groupId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupInput"}}}},"responses":{"200":{"description":"Group updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Group name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Groups"],"summary":"Delete a group","description":"Delete a group and unlink all associated links (sets group_id to null). Requires admin or owner.","parameters":[{"schema":{"type":"string","example":"grp_abc123"},"required":true,"name":"groupId","in":"path"}],"responses":{"200":{"description":"Group deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints for the current workspace","responses":{"200":{"description":"Webhook list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook endpoint (secret shown once)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookInput"}}}},"responses":{"201":{"description":"Webhook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhookId}":{"get":{"tags":["Webhooks"],"summary":"Get a single webhook endpoint","parameters":[{"schema":{"type":"string","example":"wh_abc123"},"required":true,"name":"webhookId","in":"path"}],"responses":{"200":{"description":"Webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook endpoint","parameters":[{"schema":{"type":"string","example":"wh_abc123"},"required":true,"name":"webhookId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookInput"}}}},"responses":{"200":{"description":"Webhook updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"400":{"description":"Invalid input, including adding a retired event (RETIRED_WEBHOOK_EVENT)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","parameters":[{"schema":{"type":"string","example":"wh_abc123"},"required":true,"name":"webhookId","in":"path"}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhookId}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List deliveries for a webhook endpoint (paginated)","parameters":[{"schema":{"type":"string","example":"wh_abc123"},"required":true,"name":"webhookId","in":"path"},{"schema":{"type":"integer","minimum":1,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"per_page","in":"query"},{"schema":{"type":"string","enum":["pending","delivered","retrying","failed"]},"required":false,"name":"status","in":"query"}],"responses":{"200":{"description":"Delivery list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryListResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhookId}/test":{"post":{"tags":["Webhooks"],"summary":"Queue a test ping to a webhook endpoint","description":"Queues a webhook.test delivery and returns immediately. The response only means the ping was queued; the outcome (status code, response) appears in GET /webhooks/{webhookId}/deliveries once the delivery consumer has run.","parameters":[{"schema":{"type":"string","example":"wh_abc123"},"required":true,"name":"webhookId","in":"path"}],"responses":{"202":{"description":"Test ping queued (not yet delivered)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestQueuedResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Test ping could not be queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys":{"get":{"tags":["API Keys"],"summary":"List API keys for the current workspace","responses":{"200":{"description":"API key list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["API Keys"],"summary":"Create an API key (plaintext key shown once)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyInput"}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys/{apiKeyId}":{"delete":{"tags":["API Keys"],"summary":"Revoke an API key","parameters":[{"schema":{"type":"string","example":"key_abc123"},"required":true,"name":"apiKeyId","in":"path"}],"responses":{"200":{"description":"API key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/proxy/sessions":{"get":{"tags":["Proxy Sessions"],"summary":"List active proxy sessions for the current workspace","description":"Scans one page of the workspace’s active proxy-mode links and returns those with live viewers. A viewer is a proxied page load seen in the last 30 minutes. `meta` paginates the link scan.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"name":"per_page","in":"query"}],"responses":{"200":{"description":"Active proxy sessions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProxySessionListResponse"},{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["meta"]}]}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/proxy/sessions/{slug}":{"get":{"tags":["Proxy Sessions"],"summary":"Get proxy session detail for a specific slug","parameters":[{"schema":{"type":"string","example":"my-link"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"Proxy session detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxySessionDetailResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"DO error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/summary":{"get":{"tags":["Analytics"],"summary":"Get workspace analytics summary","parameters":[{"schema":{"type":"string","enum":["1h","24h","7d","30d","90d","all"],"default":"7d"},"required":false,"name":"period","in":"query"}],"responses":{"200":{"description":"Analytics summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsSummaryResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/timeseries":{"get":{"tags":["Analytics"],"summary":"Get time-series analytics data","parameters":[{"schema":{"type":"string","enum":["1h","24h","7d","30d","90d","all"],"default":"7d"},"required":false,"name":"period","in":"query"},{"schema":{"type":"string","enum":["minute","hour","day","week","month"],"default":"day"},"required":false,"name":"granularity","in":"query"},{"schema":{"type":"string","example":"lnk_abc123"},"required":false,"name":"link_id","in":"query"},{"schema":{"type":"string","description":"Only links carrying this tag","example":"tag_abc123"},"required":false,"name":"tag_id","in":"query"},{"schema":{"type":"string","description":"Only links in this group","example":"grp_abc123"},"required":false,"name":"group_id","in":"query"}],"responses":{"200":{"description":"Time-series data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/events":{"get":{"tags":["Analytics"],"summary":"Query recent raw analytics events (placeholder for Analytics Engine SQL API)","parameters":[{"schema":{"type":"string","enum":["1h","24h","7d","30d","90d","all"],"default":"7d"},"required":false,"name":"period","in":"query"},{"schema":{"type":"string","enum":["minute","hour","day","week","month"],"default":"day"},"required":false,"name":"granularity","in":"query"},{"schema":{"type":"string","example":"lnk_abc123"},"required":false,"name":"link_id","in":"query"},{"schema":{"type":"string","description":"Only links carrying this tag","example":"tag_abc123"},"required":false,"name":"tag_id","in":"query"},{"schema":{"type":"string","description":"Only links in this group","example":"grp_abc123"},"required":false,"name":"group_id","in":"query"}],"responses":{"200":{"description":"Raw events (placeholder)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/countries":{"get":{"tags":["Analytics"],"summary":"Country breakdown of clicks","description":"Returns clicks rolled up by country for the workspace, ordered by count desc.","parameters":[{"schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"7d"},"required":false,"name":"period","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Country distribution","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string"},"available":{"type":"boolean","description":"False when the period has no country rows. Country breakdowns are not collected yet, so an empty `countries` list must be shown as \"not collected\", not as zero traffic."},"countries":{"type":"array","items":{"type":"object","properties":{"country_code":{"type":"string"},"country":{"type":"string"},"count":{"type":"integer"}},"required":["country_code","country","count"]}}},"required":["period","available","countries"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/broken-links":{"get":{"tags":["Analytics"],"summary":"List currently-broken links for the workspace","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":50,"default":10},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Broken link list","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"destination_url":{"type":"string"},"last_checked_at":{"type":"string","nullable":true}},"required":["id","slug","destination_url","last_checked_at"]}}},"required":["count","links"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/suspicious-traffic":{"get":{"tags":["Analytics"],"summary":"Suspicious / flagged traffic summary","description":"Returns total flagged vs clean click counts plus the top-N flagged links, from the hourly rollups (closed hours only). Flagged clicks are requests the per-IP fraud window flagged; they are never counted as clean.","parameters":[{"schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"7d"},"required":false,"name":"period","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":20,"default":10},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Suspicious traffic summary","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string"},"total_flagged":{"type":"integer"},"total_clean":{"type":"integer"},"top_flagged_links":{"type":"array","items":{"type":"object","properties":{"link_id":{"type":"string"},"slug":{"type":"string"},"flagged_clicks":{"type":"integer"}},"required":["link_id","slug","flagged_clicks"]}}},"required":["period","total_flagged","total_clean","top_flagged_links"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/campaigns":{"get":{"tags":["Analytics"],"summary":"UTM campaign performance breakdown","description":"Aggregates rollup clicks and conversions within the period by utm_campaign/source/medium for the workspace. uniqueVisitors is not collected yet (always 0; see uniqueVisitorsAvailable).","parameters":[{"schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"7d"},"required":false,"name":"period","in":"query"}],"responses":{"200":{"description":"Campaign performance","content":{"application/json":{"schema":{"type":"object","properties":{"uniqueVisitorsAvailable":{"type":"boolean","description":"False while unique visitors are not collected; show \"not collected\" instead of 0."},"campaigns":{"type":"array","items":{"type":"object","properties":{"campaign":{"type":"string"},"source":{"type":"string"},"medium":{"type":"string"},"clicks":{"type":"integer"},"uniqueVisitors":{"type":"integer"},"conversions":{"type":"integer"},"conversionRate":{"type":"number"}},"required":["campaign","source","medium","clicks","uniqueVisitors","conversions","conversionRate"]}}},"required":["uniqueVisitorsAvailable","campaigns"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/analytics/variants/{linkId}":{"get":{"tags":["Analytics"],"summary":"Per-variant performance for an A/B split link","description":"Per-variant clicks within the period, from the hourly variant rollups (closed hours only).","parameters":[{"schema":{"type":"string"},"required":true,"name":"linkId","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"7d"},"required":false,"name":"period","in":"query"}],"responses":{"200":{"description":"Variant stats","content":{"application/json":{"schema":{"type":"object","properties":{"uniqueVisitorsAvailable":{"type":"boolean","description":"False while unique visitors are not collected; uniqueVisitors is then 0, not a count."},"variants":{"type":"array","items":{"type":"object","properties":{"variantId":{"type":"string"},"label":{"type":"string"},"destinationUrl":{"type":"string"},"weight":{"type":"integer"},"clicks":{"type":"integer"},"uniqueVisitors":{"type":"integer","description":"Always 0 while uniqueVisitorsAvailable is false; never approximated by clicks."}},"required":["variantId","label","destinationUrl","weight","clicks","uniqueVisitors"]}}},"required":["uniqueVisitorsAvailable","variants"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cloaking-rules":{"get":{"tags":["Cloaking Rules"],"summary":"List cloaking rules","description":"Return all cloaking rules for the current workspace.","responses":{"200":{"description":"Rule list","content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"pattern":{"type":"string"},"replacement":{"type":"string"},"priority":{"type":"integer"},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","pattern","replacement","priority","active","created_at"]}}},"required":["rules"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Cloaking Rules"],"summary":"Create a cloaking rule","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"pattern":{"type":"string","minLength":1,"maxLength":512},"replacement":{"type":"string","maxLength":2048},"priority":{"type":"integer"},"active":{"type":"boolean"}},"required":["name","pattern","replacement"]}}}},"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"pattern":{"type":"string"},"replacement":{"type":"string"},"priority":{"type":"integer"},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","pattern","replacement","priority","active","created_at"]}},"required":["rule"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cloaking-rules/{ruleId}":{"patch":{"tags":["Cloaking Rules"],"summary":"Update a cloaking rule","parameters":[{"schema":{"type":"string"},"required":true,"name":"ruleId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"pattern":{"type":"string","minLength":1,"maxLength":512},"replacement":{"type":"string","maxLength":2048},"priority":{"type":"integer"},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Rule updated","content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"pattern":{"type":"string"},"replacement":{"type":"string"},"priority":{"type":"integer"},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","pattern","replacement","priority","active","created_at"]}},"required":["rule"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Cloaking Rules"],"summary":"Delete a cloaking rule","parameters":[{"schema":{"type":"string"},"required":true,"name":"ruleId","in":"path"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cloaking-rules/test":{"post":{"tags":["Cloaking Rules"],"summary":"Test a cloaking rule against sample content","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1,"maxLength":65536},"pattern":{"type":"string","minLength":1,"maxLength":512},"replacement":{"type":"string","maxLength":2048}},"required":["content","pattern","replacement"]}}}},"responses":{"200":{"description":"Test result","content":{"application/json":{"schema":{"type":"object","properties":{"result":{"type":"string"}},"required":["result"]}}}},"400":{"description":"Invalid pattern","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/embed/{linkId}/stats.json":{"get":{"tags":["Embed"],"summary":"Get embed stats for a link","description":"Returns CORS-enabled JSON with click_count (drained hourly), trend (last 24h vs the 24h before), slug, and short_url. The link badge and its workspace embeds flag are cached in KV for 5 min, and dropped as soon as embeds are disabled or the link or workspace is deleted.","parameters":[{"schema":{"type":"string"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Embed stats","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"short_url":{"type":"string","format":"uri"},"click_count":{"type":"integer"},"trend":{"type":"string","enum":["up","down","stable"]}},"required":["slug","short_url","click_count","trend"]}}}},"403":{"description":"Embeds disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/embed/{linkId}/snippet":{"get":{"tags":["Embed"],"summary":"Generate an embed code snippet for a link","description":"Returns the copy-paste HTML snippet plus the link slug and short URL.","parameters":[{"schema":{"type":"string"},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Embed snippet","content":{"application/json":{"schema":{"type":"object","properties":{"html":{"type":"string"},"linkId":{"type":"string"},"slug":{"type":"string"},"shortUrl":{"type":"string","format":"uri"}},"required":["html","linkId","slug","shortUrl"]}}}},"403":{"description":"Embeds disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/embed/widget.js":{"get":{"tags":["Embed"],"summary":"Serve embed widget JavaScript","description":"Lightweight JS that renders a click badge. Cached aggressively (24h).","responses":{"200":{"description":"Widget JS"},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/audit-log":{"get":{"tags":["Audit Log"],"summary":"List audit log entries","description":"Return paginated audit log entries for the current workspace. Requires admin or owner role.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"name":"per_page","in":"query"},{"schema":{"type":"string"},"required":false,"name":"action","in":"query"},{"schema":{"type":"string"},"required":false,"name":"actor_user_id","in":"query"},{"schema":{"type":"string"},"required":false,"name":"target_type","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"start","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"end","in":"query"}],"responses":{"200":{"description":"Audit log entries","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"actor_id":{"type":"string","nullable":true},"action":{"type":"string"},"target_type":{"type":"string"},"target_id":{"type":"string","nullable":true},"changes":{"nullable":true},"ip_address":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}},"required":["id","actor_id","action","target_type","target_id","ip_address","created_at"]}},"meta":{"type":"object","properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"}},"required":["page","per_page","total"]}},"required":["entries","meta"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications":{"get":{"tags":["Notifications"],"summary":"List notifications (stub — always empty)","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Empty list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"userId":{"type":"string"},"type":{"type":"string"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"read":{"type":"integer"},"resourceUrl":{"type":"string","nullable":true},"actorId":{"type":"string","nullable":true},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","workspaceId","userId","type","title","description","read","resourceUrl","actorId","createdAt","updatedAt"]}}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/count":{"get":{"tags":["Notifications"],"summary":"Get notification counts (stub — always zero)","responses":{"200":{"description":"Zero counts","content":{"application/json":{"schema":{"type":"object","properties":{"unread":{"type":"integer"},"total":{"type":"integer"}},"required":["unread","total"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/{id}/read":{"post":{"tags":["Notifications"],"summary":"Mark a notification read (stub — 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/notifications/read-all":{"post":{"tags":["Notifications"],"summary":"Mark all notifications read (stub - 501)","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags":{"get":{"tags":["Flags"],"summary":"List feature flags (stub — empty)","responses":{"200":{"description":"Empty list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"type":{"type":"string","enum":["boolean","string"]},"enabled":{"type":"boolean"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"integer"}},"required":["key","weight"]}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["key","type","enabled","createdAt","updatedAt"]}}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags/{key}":{"get":{"tags":["Flags"],"summary":"Evaluate a flag (stub — disabled)","parameters":[{"schema":{"type":"string"},"required":true,"name":"key","in":"path"},{"schema":{"type":"string"},"required":false,"name":"context_id","in":"query"}],"responses":{"200":{"description":"Disabled","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"value":{"anyOf":[{"type":"boolean"},{"type":"string"}]},"variantKey":{"type":"string","nullable":true}},"required":["key","value","variantKey"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Flags"],"summary":"Upsert a flag (stub — 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"key","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Flags"],"summary":"Delete a flag (stub - 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"key","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invites":{"get":{"tags":["Invites"],"summary":"List invites (stub — empty)","responses":{"200":{"description":"Empty list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"},"invitedBy":{"type":"string"},"expiresAt":{"type":"string","nullable":true},"acceptedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","workspaceId","email","role","invitedBy","expiresAt","acceptedAt","createdAt"]}}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Invites"],"summary":"Create an invite (stub — 501)","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/invites/{id}":{"get":{"tags":["Invites"],"summary":"Get an invite (stub — 404)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Invites"],"summary":"Update an invite (stub — 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}},"delete":{"tags":["Invites"],"summary":"Cancel an invite (stub - 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/invites/{id}/resend":{"post":{"tags":["Invites"],"summary":"Resend an invite email (stub — 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/invites/{id}/link":{"post":{"tags":["Invites"],"summary":"Generate an invite share link (stub — 501)","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/invites/accept":{"post":{"tags":["Invites"],"summary":"Accept an invite (stub — 501)","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not implemented","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"number"}},"required":["error","code","status"]}}}}}}},"/@{workspaceSlug}":{"get":{"tags":["Landing Pages"],"summary":"Public workspace landing page","description":"Renders a public landing page showing featured links for the workspace.","parameters":[{"schema":{"type":"string","example":"my-workspace"},"required":true,"name":"workspaceSlug","in":"path"}],"responses":{"200":{"description":"Landing page HTML"},"404":{"description":"Workspace not found or landing page disabled"},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{slug}":{"get":{"tags":["Redirect"],"summary":"Resolve a short link - redirect, interstitial, or proxy","parameters":[{"schema":{"type":"string","example":"my-link"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"HTML page (interstitial, password prompt, proxy content, or error page)"},"301":{"description":"Permanent redirect to destination URL"},"302":{"description":"Temporary redirect to destination URL"},"404":{"description":"Link not found"},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{slug}/verify-password":{"post":{"tags":["Redirect"],"summary":"Verify password for a protected link","parameters":[{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,50}$","example":"my-link"},"required":true,"name":"slug","in":"path"}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/VerifyPasswordInput"}},"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPasswordInput"}}}},"responses":{"200":{"description":"Password prompt with error message"},"302":{"description":"Redirect back to slug on success"},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{slug}/consent":{"post":{"tags":["Redirect"],"summary":"Handle GDPR consent response","parameters":[{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,50}$","example":"my-link"},"required":true,"name":"slug","in":"path"}],"responses":{"302":{"description":"Redirect back to slug with consent cookie set"},"400":{"description":"Invalid slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Cross-site consent submission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}