{"openapi":"3.1.0","info":{"title":"LeonAI Links API","version":"1.0.0","description":"REST API for the LeonAI Links platform: links, folders, analytics, QR codes, custom domains, retargeting pixels, webhooks, billing, link-in-bio, and data rights. Authenticate with a bearer JWT (dashboard sessions) or an X-API-Key (programmatic access)."},"servers":[{"url":"https://api.short.leonai.io","description":"LeonAI Links API"}],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"tags":[{"name":"Auth","description":"Signup, login, sessions, and account profile"},{"name":"Links","description":"Create and manage short links"},{"name":"Folders","description":"Organize links into folders"},{"name":"Analytics","description":"Click analytics and conversion reports"},{"name":"QR","description":"QR code generation"},{"name":"Domains","description":"Custom domains and DNS verification"},{"name":"Pixels","description":"Retargeting pixels"},{"name":"Webhooks","description":"Event subscriptions"},{"name":"Billing","description":"Subscription and billing"},{"name":"Bio","description":"Link-in-bio pages"},{"name":"API Keys","description":"Scoped keys for programmatic access"},{"name":"Teams","description":"Team membership and invitations"},{"name":"Routing","description":"Smart routing rules and A/B tests"},{"name":"Conversions","description":"Server-side conversion tracking"},{"name":"Branding","description":"White-label appearance"},{"name":"Imports","description":"Bulk import from other shorteners"},{"name":"Moderation","description":"Abuse reporting"},{"name":"Account","description":"Data rights (export, erasure)"},{"name":"Redirect","description":"Public short-link resolution"}],"paths":{"/api/v1/auth/signup":{"post":{"tags":["Auth"],"summary":"Create an account","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"name":{"type":"string"}},"required":["email","password"]}}}},"responses":{"201":{"description":"Session issued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"409":{"description":"Email already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["Auth"],"summary":"Log in (may return a 2FA challenge)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}},"required":["email","password"]}}}},"responses":{"200":{"description":"Session or 2FA challenge","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["Auth"],"summary":"Current authenticated user","responses":{"200":{"description":"The current user","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me":{"get":{"tags":["Auth"],"summary":"Who is calling: account, plan and API-key scopes","description":"Returns the caller's id, email, name, tier and email-verification state, plus `scopes` — the scopes of the API key used (null for a dashboard session, which has full access). Any valid API key may call this, whatever its scopes, so integrations can test a key before they know what it was granted. It returns nothing but the caller's own identity.","responses":{"200":{"description":"The caller","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Missing, invalid or revoked credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Subscription is not active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links":{"get":{"tags":["Links"],"summary":"List links","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"schema":{"type":"string"}},{"name":"folderId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated links","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Links"],"summary":"Create a link","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linkType":{"type":"string","enum":["url","mailto","tel","sms"],"default":"url","description":"url uses longUrl; mailto uses email/subject/body; tel uses phone; sms uses phone/body"},"longUrl":{"type":"string","format":"uri"},"email":{"type":"string","format":"email"},"subject":{"type":"string"},"phone":{"type":"string"},"body":{"type":"string"},"shortCode":{"type":"string","description":"Custom back-half (Pro+), 3-50 of [A-Za-z0-9_-]"},"domainId":{"type":"string","description":"A custom domain you own and have verified"},"folderId":{"type":["string","null"]},"title":{"type":"string","maxLength":200},"tags":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","format":"date-time","description":"Pro+"},"password":{"type":"string","description":"Pro+"},"conversionTracking":{"type":"boolean","description":"Business+"},"ogTitle":{"type":"string","maxLength":200},"ogDescription":{"type":"string","maxLength":500},"ogImage":{"type":"string","format":"uri"},"ogEnabled":{"type":"boolean"},"utmSource":{"type":"string"},"utmMedium":{"type":"string"},"utmCampaign":{"type":"string"},"utmTerm":{"type":"string"},"utmContent":{"type":"string"}}}}}},"responses":{"201":{"description":"Created link","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/{id}":{"get":{"tags":["Links"],"summary":"Get a link","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Links"],"summary":"Update a link","description":"Partial update: only the fields you send change. `{id}` is the link id or its short code. Send `null` to clear `password`, `expiresAt`, `folderId` or `domainId` (detach from a custom domain); an empty `password` string leaves the current password in place. `domainId` must be a domain you own and have verified.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linkType":{"type":"string","enum":["url","mailto","tel","sms"],"default":"url","description":"url uses longUrl; mailto uses email/subject/body; tel uses phone; sms uses phone/body"},"longUrl":{"type":"string","format":"uri"},"email":{"type":"string","format":"email"},"subject":{"type":"string"},"phone":{"type":"string"},"body":{"type":"string"},"shortCode":{"type":"string","description":"Custom back-half (Pro+), 3-50 of [A-Za-z0-9_-]"},"domainId":{"type":["string","null"]},"folderId":{"type":["string","null"]},"title":{"type":"string","maxLength":200},"tags":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":["string","null"],"format":"date-time"},"password":{"type":["string","null"]},"conversionTracking":{"type":"boolean","description":"Business+"},"ogTitle":{"type":"string","maxLength":200},"ogDescription":{"type":"string","maxLength":500},"ogImage":{"type":"string","format":"uri"},"ogEnabled":{"type":"boolean"},"utmSource":{"type":"string"},"utmMedium":{"type":"string"},"utmCampaign":{"type":"string"},"utmTerm":{"type":"string"},"utmContent":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated link","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Links"],"summary":"Delete a link","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/bulk":{"post":{"tags":["Links"],"summary":"Import many links at once","description":"Send either pre-parsed rows (`format: \"json\"`) or a raw CSV document (`format: \"csv\"`). CSV headers are matched loosely, so an export from another shortener usually imports without editing the file: `url`, `destination` and `long_url` all mean `longUrl`; `alias`, `slug` and `back_half` all mean `customCode`.\n\nRows are independent. A row that fails validation is reported in `results` and the rest still import, so one bad URL does not lose the batch. Every row runs the same checks as POST /api/v1/links, including tier gates and short-code collision. Each result echoes its input row as `input`, minus any `password`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","enum":["json","csv"],"default":"json"},"data":{"oneOf":[{"type":"array","maxItems":1000,"items":{"type":"object","properties":{"longUrl":{"type":"string","format":"uri"},"title":{"type":"string"},"customCode":{"type":"string"},"tags":{"type":"string","description":"Comma- or semicolon-separated"},"expiresAt":{"type":"string","format":"date-time"},"password":{"type":"string"},"utmSource":{"type":"string"},"utmMedium":{"type":"string"},"utmCampaign":{"type":"string"},"utmTerm":{"type":"string"},"utmContent":{"type":"string"}},"required":["longUrl"]}},{"type":"string","description":"A CSV document, when format is \"csv\""}]}},"required":["data"]}}}},"responses":{"200":{"description":"Per-row outcomes: total, successful, failed, results[]","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"No rows, more than 1000 rows, or the wrong shape for the format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/export":{"get":{"tags":["Links"],"summary":"Download every link as CSV or JSON","description":"Returns an attachment, capped at 10,000 links. Password hashes are never included — only whether a link has one. CSV cells that a spreadsheet would evaluate as a formula are prefixed with an apostrophe, and the human-readable column headers map back onto import field names, so an export can be edited in a spreadsheet and fed straight back to POST /api/v1/links/bulk.","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["csv","json"],"default":"csv"}}],"responses":{"200":{"description":"A text/csv or application/json attachment"},"400":{"description":"format must be 'csv' or 'json'","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{id}/detect-provider":{"get":{"tags":["Domains"],"summary":"Detect where the domain's DNS is hosted","description":"Resolves the authoritative nameservers over DNS-over-HTTPS to identify the DNS host and whether we can configure it for you. Never fails on a lookup error — an unresolvable domain comes back as `provider: \"unknown\"` with `autoConnectSupported: false`, which is a result the UI shows rather than an error. The detected provider is cached on the domain record.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"provider, nameservers, autoConnectSupported, detail","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{id}/auto-configure":{"post":{"tags":["Domains"],"summary":"Create the DNS records for you (Cloudflare-hosted domains)","description":"Writes both records the domain needs — the `_leonai-verify` TXT proof and the proxied CNAME to the edge — using a Cloudflare API token you supply.\n\n**The token is used for this one request and then discarded.** It is never stored, logged, or returned. Re-running this later means supplying it again, which is the deliberate trade: a token that can rewrite your DNS is not a secret we want to hold.\n\nCreate the token at *My Profile → API Tokens* with the **Edit zone DNS** template, scoped to this zone only. Idempotent: running it twice updates the existing records rather than duplicating them.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["cloudflare"],"default":"cloudflare","description":"Only Cloudflare is supported today."},"apiToken":{"type":"string","description":"Cloudflare API token with Zone:DNS:Edit on this zone. Not stored."}},"required":["apiToken"]}}}},"responses":{"200":{"description":"The domain, plus a connection object with provider/verified/sslEnabled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"apiToken missing, unsupported provider, token rejected, or no zone found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Could not reach the Cloudflare API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/folders":{"get":{"tags":["Folders"],"summary":"List folders","responses":{"200":{"description":"Folders","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Folders"],"summary":"Create a folder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"parentId":{"type":"string"}},"required":["name"]}}}},"responses":{"201":{"description":"Created folder","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/analytics/account":{"get":{"tags":["Analytics"],"summary":"Account-wide analytics","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Summary + timeline","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/analytics/links/{id}":{"get":{"tags":["Analytics"],"summary":"Analytics for one link","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Summary, timeline, breakdowns","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{shortCode}/qr":{"get":{"tags":["QR"],"summary":"QR code image for a short link","description":"Public and cacheable for 24h. Colors may be sent with or without a leading \"#\". Unlike the authenticated bulk endpoint this one takes no `logo`: fetching an arbitrary logo URL on an unauthenticated route would be an SSRF vector.","security":[],"parameters":[{"name":"shortCode","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["png","svg"]}},{"name":"size","in":"query","required":false,"schema":{"type":"integer"}},{"name":"margin","in":"query","required":false,"schema":{"type":"integer"}},{"name":"color","in":"query","required":false,"schema":{"type":"string"}},{"name":"backgroundColor","in":"query","required":false,"schema":{"type":"string"}},{"name":"errorCorrectionLevel","in":"query","required":false,"schema":{"type":"string","enum":["L","M","Q","H"]}}],"responses":{"200":{"description":"QR image (image/png or image/svg+xml)"},"404":{"description":"Unknown short code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Link has expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{shortCode}/verify":{"post":{"tags":["Redirect"],"summary":"Unlock a password-protected short link","description":"Accepts JSON or a form post. Returns the destination on success.","security":[],"parameters":[{"name":"shortCode","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string"}},"required":["password"]}}}},"responses":{"200":{"description":"Password accepted; destination returned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Password missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Wrong password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown short code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains":{"get":{"tags":["Domains"],"summary":"List custom domains","responses":{"200":{"description":"Domains","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Domains"],"summary":"Add a custom domain","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"}},"required":["domain"]}}}},"responses":{"201":{"description":"Created domain","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"409":{"description":"Already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{id}/verification":{"get":{"tags":["Domains"],"summary":"Get the DNS TXT record to publish for verification","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"TXT record + instructions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{id}/verify":{"post":{"tags":["Domains"],"summary":"Verify domain ownership via a live DNS TXT lookup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verified","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Expected TXT record not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"DNS lookup failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pixels":{"get":{"tags":["Pixels"],"summary":"List retargeting pixels","responses":{"200":{"description":"Pixels","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Pixels"],"summary":"Create a retargeting pixel (Business+)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["meta","google_ads","linkedin","tiktok","twitter"]},"pixelId":{"type":"string"},"name":{"type":"string"}},"required":["provider","pixelId"]}}}},"responses":{"201":{"description":"Created pixel","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Tier too low","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhooks","responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"}},"required":["url","events"]}}}},"responses":{"201":{"description":"Created webhook (secret revealed once)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/billing/plans":{"get":{"tags":["Billing"],"summary":"Public billing catalogue","security":[],"responses":{"200":{"description":"Plans + public config","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/billing/subscription":{"get":{"tags":["Billing"],"summary":"Current subscription","responses":{"200":{"description":"Subscription","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/bio-pages":{"get":{"tags":["Bio"],"summary":"List link-in-bio pages","responses":{"200":{"description":"Bio pages","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Bio"],"summary":"Create a link-in-bio page","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"}},"required":["slug","title"]}}}},"responses":{"201":{"description":"Created page","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/users/me/export":{"get":{"tags":["Account"],"summary":"Export all of your data (GDPR/CCPA portability)","responses":{"200":{"description":"A JSON attachment with all account data (secrets excluded)","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/users/me/consent":{"get":{"tags":["Account"],"summary":"Your current cookie and marketing consent","description":"The effective state, derived from the newest decision in an append-only log. An account that has never been asked reads as `false` for both categories: consent must be opt-in, so silence is not agreement. `essential` is always true because strictly necessary cookies cannot be declined.","responses":{"200":{"description":"essential, analytics, marketing, updatedAt, gpc","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Account"],"summary":"Record a consent decision","description":"Appends a new record per category — nothing is overwritten. A withdrawal is a new row rather than an edit, so the evidence that earlier processing was lawful survives the opt-out; records are retained for 5 years.\n\nBoth categories are required: a partial update would leave the log unable to say what the user was actually shown. Setting `gpc: true` records that a Global Privacy Control signal drove the choice, and is rejected alongside a grant, because GPC is a binding opt-out under CCPA/CPRA.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"analytics":{"type":"boolean"},"marketing":{"type":"boolean"},"source":{"type":"string","enum":["banner","preferences","api"],"default":"api","description":"Where the choice was made, for answering later disputes."},"gpc":{"type":"boolean","default":false,"description":"True when a Global Privacy Control signal forced the choice."}},"required":["analytics","marketing"]}}}},"responses":{"200":{"description":"The updated effective consent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Missing category, unknown source, or consent granted under GPC","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/users/me":{"delete":{"tags":["Account"],"summary":"Delete your account (GDPR erasure)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirm":{"type":"string","enum":["DELETE"]}},"required":["confirm"]}}}},"responses":{"200":{"description":"Account anonymized","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Confirmation required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["Auth"],"summary":"Create an account (alias of /auth/signup)","description":"Identical to POST /api/v1/auth/signup. Both names exist because early integrations were written against /register; neither is deprecated.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"name":{"type":"string"}},"required":["email","password"]}}}},"responses":{"201":{"description":"Session issued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"409":{"description":"Email already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["Auth"],"summary":"Exchange a refresh token for a new access token","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string"}},"required":["refreshToken"]}}}},"responses":{"200":{"description":"New token pair","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Refresh token invalid, expired, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["Auth"],"summary":"Revoke the current session","responses":{"200":{"description":"Session revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password reset email","description":"Always answers 200, whether or not the address has an account — a different answer would let anyone test which emails are registered.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Reset email sent if the account exists","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Set a new password using a reset token","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8}},"required":["token","password"]}}}},"responses":{"200":{"description":"Password changed; all other sessions revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Token invalid or expired, or password too weak","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/verify-email":{"post":{"tags":["Auth"],"summary":"Confirm an email address","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"Email verified","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Token invalid or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/resend-verification":{"post":{"tags":["Auth"],"summary":"Send the verification email again","responses":{"200":{"description":"Verification email sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"429":{"description":"Too many requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/2fa/status":{"get":{"tags":["Auth"],"summary":"Whether TOTP is enabled, and how many recovery codes remain","responses":{"200":{"description":"2FA status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/2fa/setup":{"post":{"tags":["Auth"],"summary":"Begin TOTP enrolment","description":"Returns the shared secret and otpauth:// URI. Not active until /2fa/enable.","responses":{"200":{"description":"Secret + otpauth URI","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/2fa/enable":{"post":{"tags":["Auth"],"summary":"Activate TOTP by confirming a code","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"6-digit code"}},"required":["token"]}}}},"responses":{"200":{"description":"2FA enabled; recovery codes revealed once","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Code incorrect or enrolment not started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/2fa/disable":{"post":{"tags":["Auth"],"summary":"Turn TOTP off","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Current 6-digit code"},"recoveryCode":{"type":"string","description":"Use instead of token if the device is lost"}}}}}},"responses":{"200":{"description":"2FA disabled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Code incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/2fa/verify":{"post":{"tags":["Auth"],"summary":"Complete a login that returned a 2FA challenge","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pendingToken":{"type":"string","description":"From the login response"},"token":{"type":"string","description":"6-digit code"},"recoveryCode":{"type":"string","description":"Single-use, consumed on success"}},"required":["pendingToken"]}}}},"responses":{"200":{"description":"Session issued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Challenge failed or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/oauth/providers":{"get":{"tags":["Auth"],"summary":"Which SSO providers are configured on this deployment","security":[],"responses":{"200":{"description":"Enabled provider ids","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/auth/oauth/{provider}/start":{"get":{"tags":["Auth"],"summary":"Begin an SSO login (redirects to the provider)","security":[],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}},{"name":"redirectTo","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to the provider's consent screen"},"404":{"description":"Provider not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/oauth/{provider}/callback":{"get":{"tags":["Auth"],"summary":"Provider redirect target","description":"Called by the identity provider, not by your code. Redirects back to the dashboard with a one-time code to hand to /auth/oauth/exchange.","security":[],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}},{"name":"code","in":"query","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"error","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back to the dashboard"}}}},"/api/v1/auth/oauth/exchange":{"post":{"tags":["Auth"],"summary":"Trade the one-time SSO code for a session","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"}},"required":["code"]}}}},"responses":{"200":{"description":"Session issued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Code invalid or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/api-keys":{"get":{"tags":["API Keys"],"summary":"List your API keys","description":"Only the prefix of each key is returned; the secret is never retrievable.","responses":{"200":{"description":"Keys with prefix, scopes, and last use","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["API Keys"],"summary":"Create an API key","description":"The full key is shown once in this response and cannot be recovered later.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["links:read","links:write","analytics:read","domains:read","domains:write"]}},"environment":{"type":"string","enum":["live","test"]}},"required":["name"]}}}},"responses":{"201":{"description":"Created key (secret revealed once)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Key limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/api-keys/{id}":{"patch":{"tags":["API Keys"],"summary":"Rename a key or change its scopes","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated key","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["API Keys"],"summary":"Revoke a key","description":"Takes effect immediately. Integrations using it start receiving 401.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analytics/conversions/summary":{"get":{"tags":["Analytics"],"summary":"Account-wide conversion totals","parameters":[{"name":"startDate","in":"query","required":false,"schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Counts and value by conversion name","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/analytics/{linkId}/conversions":{"get":{"tags":["Analytics"],"summary":"Conversions attributed to one link","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"startDate","in":"query","required":false,"schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Conversions for the link","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analytics/{linkId}/share":{"get":{"tags":["Analytics"],"summary":"List share links for a link report","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Existing share tokens","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Analytics"],"summary":"Create a public, read-only analytics link","description":"Anyone holding the returned token can read the report without signing in.","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expiresAt":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"Share token + URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analytics/shares/{id}":{"delete":{"tags":["Analytics"],"summary":"Revoke a shared analytics link","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/analytics/{token}":{"get":{"tags":["Analytics"],"summary":"Read a shared analytics report","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The shared report","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Token unknown, revoked, or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/conversions":{"post":{"tags":["Conversions"],"summary":"Record a conversion","description":"Attribute a signup, sale, or other goal back to a click. Supply clickId when you have it (most precise) or linkId to attribute at link level.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Goal name, e.g. \"purchase\""},"linkId":{"type":"string"},"clickId":{"type":"string"},"value":{"type":"number"},"currency":{"type":"string","description":"ISO 4217, e.g. \"USD\""},"externalId":{"type":"string","description":"Your own id; makes retries idempotent"},"metadata":{"type":"object"}},"required":["name"]}}}},"responses":{"201":{"description":"Conversion recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/checkout":{"post":{"tags":["Billing"],"summary":"Start a subscription checkout","description":"Returns the payment-form details used to collect a card via Sumit.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tier":{"type":"string","enum":["pro","business","enterprise"]},"interval":{"type":"string","enum":["monthly","yearly"]},"legalName":{"type":"string"},"companyNumber":{"type":"string"}},"required":["tier"]}}}},"responses":{"200":{"description":"Checkout session","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Unknown tier or interval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/confirm":{"post":{"tags":["Billing"],"summary":"Complete checkout with a tokenized card","description":"Safe to retry: a repeated call with the same identifier returns the original result instead of charging twice.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"identifier":{"type":"string","description":"From the checkout response"},"singleUseToken":{"type":"string","description":"Card token; raw card data never reaches this API"},"cardLast4":{"type":"string"},"cardExpMonth":{"type":"integer"},"cardExpYear":{"type":"integer"}},"required":["identifier","singleUseToken"]}}}},"responses":{"200":{"description":"Subscription active","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Token rejected or checkout expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment declined","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/cancel":{"post":{"tags":["Billing"],"summary":"Cancel at the end of the paid period","description":"Access continues until the period ends; nothing is pro-rated.","responses":{"200":{"description":"Cancellation scheduled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"No active subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/resume":{"post":{"tags":["Billing"],"summary":"Undo a scheduled cancellation","responses":{"200":{"description":"Subscription resumed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Nothing to resume","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/invoices":{"get":{"tags":["Billing"],"summary":"List invoices and their document links","responses":{"200":{"description":"Invoices","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}}},"/api/v1/billing/sumit-webhook":{"post":{"tags":["Billing"],"summary":"Payment provider callback","description":"Called by Sumit, not by your code. Authenticated by a shared secret in the query string rather than by a session.","security":[],"parameters":[{"name":"secret","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Event processed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"401":{"description":"Bad secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/folders/{id}":{"patch":{"tags":["Folders"],"summary":"Rename or re-parent a folder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"parentId":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Updated folder","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Folders"],"summary":"Delete a folder","description":"Links inside are moved out, not deleted.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/folders/{id}/links":{"put":{"tags":["Folders"],"summary":"Replace the set of links in a folder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linkIds":{"type":"array","items":{"type":"string"}}},"required":["linkIds"]}}}},"responses":{"200":{"description":"Folder contents replaced","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{id}":{"get":{"tags":["Domains"],"summary":"Get a custom domain","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The domain","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Domains"],"summary":"Update domain settings","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean"},"sslEnabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated domain","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Domains"],"summary":"Remove a custom domain","description":"Links on this domain stop resolving.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/{linkId}/routing":{"get":{"tags":["Routing"],"summary":"List routing rules for a link","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rules in priority order","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Routing"],"summary":"Add a routing rule (Business+)","description":"Rules are evaluated in priority order; the first match wins. For ab_test rules, weight sets the share of traffic.","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["geo","device","time","language","ab_test"]},"destinationUrl":{"type":"string","format":"uri"},"name":{"type":"string"},"conditions":{"type":"object"},"priority":{"type":"integer"},"weight":{"type":"integer"},"enabled":{"type":"boolean"}},"required":["type","destinationUrl"]}}}},"responses":{"201":{"description":"Created rule","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Tier too low","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/{linkId}/routing/{ruleId}":{"get":{"tags":["Routing"],"summary":"Get one routing rule","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"ruleId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The rule","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Routing"],"summary":"Update a routing rule","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"ruleId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"destinationUrl":{"type":"string","format":"uri"},"name":{"type":"string"},"conditions":{"type":"object"},"priority":{"type":"integer"},"weight":{"type":"integer"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated rule","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Routing"],"summary":"Delete a routing rule","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"ruleId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/{linkId}/ab-report":{"get":{"tags":["Routing"],"summary":"A/B test results for a link","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"startDate","in":"query","required":false,"schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Per-variant clicks and uplift","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pixels/{id}":{"patch":{"tags":["Pixels"],"summary":"Update a retargeting pixel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"pixelId":{"type":"string"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated pixel","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Pixels"],"summary":"Delete a retargeting pixel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/links/{linkId}/pixels":{"get":{"tags":["Pixels"],"summary":"Which pixels fire on a link","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attached pixels","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Pixels"],"summary":"Replace the pixels attached to a link","parameters":[{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pixelIds":{"type":"array","items":{"type":"string"}}},"required":["pixelIds"]}}}},"responses":{"200":{"description":"Attachments replaced","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Get a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The webhook","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated webhook","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"Recent delivery attempts, with status and response code","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Delivery log","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams":{"get":{"tags":["Teams"],"summary":"Teams you belong to","responses":{"200":{"description":"Teams with your role in each","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"post":{"tags":["Teams"],"summary":"Create a team","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}}},"responses":{"201":{"description":"Created team","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"409":{"description":"You already own a team","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/invites/accept":{"post":{"tags":["Teams"],"summary":"Accept an invitation","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"From the invite email"}},"required":["token"]}}}},"responses":{"200":{"description":"Joined the team","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Invite was issued to a different address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Invite expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{teamId}/members":{"get":{"tags":["Teams"],"summary":"List team members","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Members and roles","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Not a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{teamId}/members/{memberId}":{"patch":{"tags":["Teams"],"summary":"Change a member's role","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}},{"name":"memberId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["admin","member","viewer"]}},"required":["role"]}}}},"responses":{"200":{"description":"Role updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Requires owner or admin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Teams"],"summary":"Remove a member","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}},{"name":"memberId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Requires owner or admin; the owner cannot be removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{teamId}/invites":{"get":{"tags":["Teams"],"summary":"List pending invitations","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pending invites","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Not a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Teams"],"summary":"Invite someone by email","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["admin","member","viewer"]}},"required":["email"]}}}},"responses":{"201":{"description":"Invitation sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Requires owner or admin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already invited or already a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{teamId}/invites/{inviteId}":{"delete":{"tags":["Teams"],"summary":"Revoke a pending invitation","parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string"}},{"name":"inviteId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}":{"get":{"tags":["Bio"],"summary":"Get a bio page with its items","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The page","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Bio"],"summary":"Update a bio page","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"bio":{"type":"string"},"theme":{"type":"string"},"avatarUrl":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Updated page","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Bio"],"summary":"Delete a bio page","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/publish":{"post":{"tags":["Bio"],"summary":"Make a bio page publicly visible","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/unpublish":{"post":{"tags":["Bio"],"summary":"Hide a bio page from the public URL","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Unpublished","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/avatar":{"post":{"tags":["Bio"],"summary":"Upload a page avatar","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Image file (PNG, JPEG, or WebP)","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"Avatar stored; URL returned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Unsupported type or file too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Bio"],"summary":"Remove the page avatar","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Avatar removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/items":{"post":{"tags":["Bio"],"summary":"Add an item to a bio page","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["link","header","text","social","email_capture"]},"title":{"type":"string"},"url":{"type":"string","format":"uri"},"linkId":{"type":"string","description":"Point at one of your short links instead of a raw URL"},"icon":{"type":"string"},"position":{"type":"integer"},"enabled":{"type":"boolean"}},"required":["type"]}}}},"responses":{"201":{"description":"Created item","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/items/order":{"put":{"tags":["Bio"],"summary":"Reorder items","description":"Send every item id in the order you want them displayed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"itemIds":{"type":"array","items":{"type":"string"}}},"required":["itemIds"]}}}},"responses":{"200":{"description":"Order saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Item list does not match the page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/items/{itemId}":{"patch":{"tags":["Bio"],"summary":"Update an item","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string","format":"uri"},"linkId":{"type":"string"},"icon":{"type":"string"},"position":{"type":"integer"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated item","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Bio"],"summary":"Delete an item","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/items/{itemId}/thumbnail":{"post":{"tags":["Bio"],"summary":"Upload an item thumbnail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Image file (PNG, JPEG, or WebP)","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"Thumbnail stored; URL returned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Unsupported type or file too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Bio"],"summary":"Remove an item thumbnail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Thumbnail removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/subscribers":{"get":{"tags":["Bio"],"summary":"List email-capture subscribers","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"Subscribers (JSON, or a CSV attachment)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/subscribers/{subscriberId}":{"delete":{"tags":["Bio"],"summary":"Delete a subscriber","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"subscriberId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bio-pages/{id}/analytics":{"get":{"tags":["Bio"],"summary":"Views and per-item clicks","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Views, clicks, and timeline","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bio/{slug}":{"get":{"tags":["Bio"],"summary":"Read a published bio page","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The published page","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Unknown or unpublished","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bio/{slug}/view":{"post":{"tags":["Bio"],"summary":"Record a page view","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Recorded"}}}},"/api/v1/public/bio/{slug}/subscribe":{"post":{"tags":["Bio"],"summary":"Submit an email-capture form","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"itemId":{"type":"string","description":"Which capture block was submitted"}},"required":["email"]}}}},"responses":{"201":{"description":"Subscribed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Invalid email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bio/{slug}/items/{itemId}/click":{"post":{"tags":["Bio"],"summary":"Record a click on a bio item","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Recorded"}}}},"/api/v1/public/media/{path}":{"get":{"tags":["Bio"],"summary":"Serve uploaded bio media (avatars, thumbnails)","security":[],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The image"},"404":{"description":"Unknown media key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/branding":{"get":{"tags":["Branding"],"summary":"Current white-label settings","responses":{"200":{"description":"Branding settings","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}}}},"put":{"tags":["Branding"],"summary":"Update white-label settings (Enterprise)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"brandName":{"type":"string"},"brandColor":{"type":"string","description":"Hex, e.g. \"#1d4ed8\""},"logoUrl":{"type":"string","format":"uri"},"hidePoweredBy":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated branding","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"403":{"description":"Tier too low","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/imports/bitly":{"post":{"tags":["Imports"],"summary":"Import links from Bitly","description":"Run with dryRun first to see what would be imported without writing anything.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accessToken":{"type":"string","description":"A Bitly API token; not stored"},"dryRun":{"type":"boolean"}},"required":["accessToken"]}}}},"responses":{"200":{"description":"Import summary","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Bitly rejected the token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tier too low","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/qr/bulk":{"post":{"tags":["QR"],"summary":"Generate QR codes for many short codes at once","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shortCodes":{"type":"array","items":{"type":"string"}},"format":{"type":"string","enum":["png","svg"]},"size":{"type":"integer"},"margin":{"type":"integer"},"color":{"type":"string"},"backgroundColor":{"type":"string"},"errorCorrectionLevel":{"type":"string","enum":["L","M","Q","H"]},"logo":{"type":"string","description":"Logo to overlay on SVG output, inline: a data URI or base64 PNG, JPEG, GIF or WebP, at most 2 MB. A URL is refused (never fetched). Not drawn on png/dataurl."}},"required":["shortCodes"]}}}},"responses":{"200":{"description":"One result per short code","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/report/{shortCode}":{"post":{"tags":["Moderation"],"summary":"Report a short link for abuse","security":[],"parameters":[{"name":"shortCode","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["spam","phishing","malware","illegal","other"]},"details":{"type":"string"}},"required":["reason"]}}}},"responses":{"202":{"description":"Report queued for review","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{}},"required":["success"]}}}},"404":{"description":"Unknown short code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{shortCode}":{"get":{"tags":["Redirect"],"summary":"Resolve a short code (public redirect)","security":[],"parameters":[{"name":"shortCode","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to the destination URL"},"404":{"description":"Unknown short code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Dashboard session token: `Authorization: Bearer <token>`."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Programmatic API key (scoped): `X-API-Key: leonai_live_...`."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Short error label"},"message":{"type":"string","description":"Human-readable detail"}},"required":["error","message"]}}}}