{"openapi":"3.1.0","info":{"title":"U2L API","version":"1.0.0","description":"The U2L public REST API. Create and manage short links and QR codes, organize them into folders, and read click analytics programmatically. All endpoints are authenticated with a Bearer API key (`u2l_live_...`) created in the U2L dashboard under Settings > API. API access is available on every plan; request rates and creation velocity are limited per plan.","contact":{"name":"U2L AI Support","email":"support@u2l.ai","url":"https://u2l.ai/contact"}},"servers":[{"url":"https://u2l.ai","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Links","description":"Create, list, read, update, and delete short links and QR codes."},{"name":"Folders","description":"Organize links into folders."},{"name":"Analytics","description":"Click analytics for links and account-wide stats."},{"name":"Domains","description":"Domains available to your account for link creation."},{"name":"Account","description":"Account profile and usage."},{"name":"Webhooks","description":"Manage outgoing webhook endpoints. U2L POSTs link.created / link.updated / link.deleted events to your URL, signed with a per-endpoint secret. Pro plan and above."}],"paths":{"/api/v1/links":{"post":{"tags":["Links"],"summary":"Create a link","operationId":"createLink","description":"Creates a short link or QR code. By default the domain is chosen automatically: destinations recognized as trusted land on `u2l.ai`, everything else on `u.gy` - this never errors. Pass a specific `domain` to use one of your subdomains or verified custom domains instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkRequest"},"example":{"url":"https://example.com/my-page","alias":"my-link","title":"My example link"}}}},"responses":{"201":{"description":"Link created.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkResponse"}}}},"400":{"$ref":"#/components/responses/SafetyBlocked"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The requested alias is already taken or reserved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Alias \"my-link\" is already taken on u.gy."}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Links"],"summary":"List links","operationId":"listLinks","description":"Returns your links, newest first by default, with pagination. Filter by type, domain, folder, tag, or free-text search across slug, destination, and title.","parameters":[{"name":"page","in":"query","description":"Page number (1-based).","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","description":"Results per page (max 100).","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"type","in":"query","description":"Filter by link type.","schema":{"type":"string","enum":["SL","QR"]}},{"name":"domain","in":"query","description":"Filter by domain.","schema":{"type":"string"}},{"name":"folderId","in":"query","description":"Filter by folder ID.","schema":{"type":"string"}},{"name":"tag","in":"query","description":"Filter by tag.","schema":{"type":"string"}},{"name":"search","in":"query","description":"Free-text search across slug, destination, and title.","schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort field.","schema":{"type":"string","enum":["createdAt","title","clicks"],"default":"createdAt"}},{"name":"order","in":"query","description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A page of links.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/Link"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/links/{domain}/{slug}":{"get":{"tags":["Links"],"summary":"Get a link","operationId":"getLink","description":"Returns a single link, including its total click count.","parameters":[{"$ref":"#/components/parameters/LinkDomain"},{"$ref":"#/components/parameters/LinkSlug"}],"responses":{"200":{"description":"The link.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Links"],"summary":"Update a link","operationId":"updateLink","description":"Updates a link. Only the fields you send are changed. Send `password: null` to remove a password; send `expiresAt: null` to remove expiration. Destination changes on `u2l.ai` links re-run the trust check - untrusted destinations are rejected (move the link to another domain instead).","parameters":[{"$ref":"#/components/parameters/LinkDomain"},{"$ref":"#/components/parameters/LinkSlug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkRequest"},"example":{"destination":"https://example.com/new-page","title":"Updated title"}}}},"responses":{"200":{"description":"The updated link.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"$ref":"#/components/responses/SafetyBlocked"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Links"],"summary":"Delete a link","operationId":"deleteLink","description":"Permanently deletes a link. The short URL stops resolving immediately. This cannot be undone.","parameters":[{"$ref":"#/components/parameters/LinkDomain"},{"$ref":"#/components/parameters/LinkSlug"}],"responses":{"200":{"description":"Link deleted.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"},"example":{"message":"Link deleted successfully"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/folders":{"get":{"tags":["Folders"],"summary":"List folders","operationId":"listFolders","description":"Returns all your folders with the number of links in each, newest first.","responses":{"200":{"description":"Your folders.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/Folder"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Folders"],"summary":"Create a folder","operationId":"createFolder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Folder name.","example":"Marketing campaigns"}}}}}},"responses":{"201":{"description":"Folder created.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Folder"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/folders/{folderId}":{"patch":{"tags":["Folders"],"summary":"Rename a folder","operationId":"renameFolder","parameters":[{"$ref":"#/components/parameters/FolderId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"New folder name.","example":"Q3 campaigns"}}}}}},"responses":{"200":{"description":"Folder renamed.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"message":{"type":"string"}}},"example":{"id":"9f0c2f9e-...","name":"Q3 campaigns","message":"Folder renamed successfully"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Folders"],"summary":"Delete a folder","operationId":"deleteFolder","description":"Deletes a folder. Links inside it are NOT deleted - they are moved out of the folder.","parameters":[{"$ref":"#/components/parameters/FolderId"}],"responses":{"200":{"description":"Folder deleted.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"},"example":{"message":"Folder deleted successfully"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/analytics/{domain}/{slug}":{"get":{"tags":["Analytics"],"summary":"Get link analytics","operationId":"getLinkAnalytics","description":"Click analytics for a single link: totals plus breakdowns by country, device, browser, referrer, and operating system. Requires a plan with Analytics API access (Advanced plan or above).","parameters":[{"$ref":"#/components/parameters/LinkDomain"},{"$ref":"#/components/parameters/LinkSlug"},{"name":"timeRange","in":"query","description":"Number of days to include in `clicksInRange` and the breakdowns. Use `0` for all time.","schema":{"type":"integer","default":30}}],"responses":{"200":{"description":"Analytics for the link.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkAnalytics"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/stats":{"get":{"tags":["Analytics"],"summary":"Get account stats","operationId":"getAccountStats","description":"Account-wide totals: links, QR codes, all-time clicks, this week's clicks, plus your plan limits.","responses":{"200":{"description":"Account stats.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStats"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/domains":{"get":{"tags":["Domains"],"summary":"List domains","operationId":"listDomains","description":"All domains you can create links on: the automatic default, U2L's first-party short domains, your claimed subdomains, and your custom domains. Domains are added and verified in the dashboard, not via the API.","responses":{"200":{"description":"Available domains.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/account":{"get":{"tags":["Account"],"summary":"Get account info","operationId":"getAccount","description":"Your account profile and current usage against plan limits.","responses":{"200":{"description":"Account info.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhooks","description":"List all webhook endpoints on your account. The signing secret is never returned after creation.","operationId":"listWebhooks","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}},"example":{"webhooks":[{"id":"we_f9ae3c2a1ee24e1b9321e359c9830b96","url":"https://example.com/u2l-webhook","events":["link.created","link.updated","link.deleted"],"active":true,"failureCount":0,"lastDeliveryAt":"2026-07-25 09:12:44","createdAt":"2026-07-25 08:52:02"}]}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook","description":"Register an HTTPS endpoint to receive link events. Requires the Pro plan or above; each plan has a maximum number of endpoints (Pro 3, Growth 5, Advanced 10, Team 20, Enterprise 50). The response includes the signing secret exactly once.","operationId":"createWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookResponse"},"example":{"id":"we_f9ae3c2a1ee24e1b9321e359c9830b96","url":"https://example.com/u2l-webhook","events":["link.created","link.updated","link.deleted"],"active":true,"failureCount":0,"lastDeliveryAt":null,"createdAt":"2026-07-25 08:52:02","secret":"u2lwh_4f2axxxx91c3xxxx7e5bxxxx82d1xxxx3c9fxxxx6a04xxxx"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}":{"patch":{"tags":["Webhooks"],"summary":"Update a webhook","description":"Change the URL, subscribed events, or active state. Setting active to true on a disabled endpoint resets its failure count.","operationId":"updateWebhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"},"example":{"id":"we_f9ae3c2a1ee24e1b9321e359c9830b96","url":"https://example.com/u2l-webhook","events":["link.created"],"active":true,"failureCount":0,"lastDeliveryAt":"2026-07-25 09:12:44","createdAt":"2026-07-25 08:52:02"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","description":"Remove a webhook endpoint. Pending deliveries to it are dropped.","operationId":"deleteWebhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"},"example":{"message":"Webhook deleted successfully"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"u2l_live_...","description":"API key from the U2L dashboard (Settings > API). Format: `u2l_live_` followed by 32 hex characters."}},"parameters":{"LinkDomain":{"name":"domain","in":"path","required":true,"description":"The link's domain (e.g. `u.gy` or `u2l.ai`).","schema":{"type":"string","example":"u.gy"}},"LinkSlug":{"name":"slug","in":"path","required":true,"description":"The link's slug (the part after the domain).","schema":{"type":"string","example":"my-link"}},"FolderId":{"name":"folderId","in":"path","required":true,"description":"The folder's ID.","schema":{"type":"string"}}},"headers":{"XRateLimitLimit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"XRateLimitReset":{"description":"Unix timestamp when the window resets.","schema":{"type":"integer"}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"Invalid API key."}}}}},"Forbidden":{"description":"Your plan does not include this feature, or a limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden","message":"Link limit reached for your plan."}}}}},"NotFound":{"description":"The resource does not exist or is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Link not found."}}}}},"ValidationError":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"url is required."}}}}},"SafetyBlocked":{"description":"The destination was rejected by safety or trust checks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"This link was flagged by our safety checks."}}}}},"RateLimited":{"description":"Rate limit exceeded. Check X-RateLimit-Reset for when the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Rate limit exceeded. Try again later."}}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable explanation."}}}}},"Message":{"type":"object","properties":{"message":{"type":"string"}}},"Link":{"type":"object","properties":{"id":{"type":"string","description":"Link ID (UUID)."},"slug":{"type":"string","description":"The part after the domain."},"domain":{"type":"string","description":"The link's domain."},"destination":{"type":"string","description":"Where the link redirects to."},"title":{"type":"string","description":"Optional title."},"tags":{"type":"array","items":{"type":"string"}},"hasPassword":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time","description":"Expiration time, if set."},"permanent":{"type":"boolean","description":"true = 301 redirect, false = 302."},"type":{"type":"string","enum":["SL","QR","PG"],"description":"SL = short link, QR = QR code, PG = link-in-bio page."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"folderId":{"type":"string"},"clicks":{"type":"integer","description":"Total clicks."},"ogTitle":{"type":"string","description":"Custom social preview title."},"ogDescription":{"type":"string"},"ogImage":{"type":"string"},"abTestEnabled":{"type":"boolean"},"destinationB":{"type":"string","description":"A/B variant destination."},"splitPercentage":{"type":"integer","description":"Traffic percentage to destination A."},"qrStyle":{"type":"object","description":"QR styling options (QR links only)."},"externalBrowser":{"type":"boolean","description":"Open in the phone's default browser when tapped inside in-app browsers (Android)."},"cloaking":{"type":"boolean","description":"URL cloaking (Advanced+): serve the destination inside a frame so visitors keep seeing the short URL."},"deepLink":{"type":"boolean","description":"Open the destination in its native mobile app (YouTube, Instagram, etc.) when the visitor has it installed."},"routing":{"$ref":"#/components/schemas/RoutingRules"},"maxClicks":{"type":"integer","minimum":1,"description":"Expire the link after this many human clicks (Growth+). Enforcement is eventual: the link stops within a few minutes of the limit; bot traffic never counts."},"expiredUrl":{"type":"string","format":"uri","description":"Where visitors are redirected (302) once the link has expired by date or click limit. Unset shows the standard expired page."},"startsAt":{"type":"string","format":"date-time","description":"Before this time the link serves a not-active-yet page. Must be before expiresAt when both are set."},"clickCount":{"type":"integer","description":"Human clicks counted toward maxClicks. Present only when maxClicks is set."},"rotation":{"$ref":"#/components/schemas/Rotation"},"pixels":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/Pixel"},"description":"Retargeting pixels (Advanced+). Fired from a brief redirect page for human visitors only."},"appFallback":{"type":"string","enum":["store"],"description":"App deep links (Advanced+): 'store' sends mobile visitors without the domain's app installed to the App Store / Play Store (with the link carried in the Play install referrer). Requires a mobile app configured on the link's domain in the dashboard; visitors with the app installed open it directly via the domain's universal/app links."}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"},"hasMore":{"type":"boolean"}}},"CustomOg":{"type":"object","description":"Custom social preview (Advanced plan or above).","properties":{"title":{"type":"string"},"description":{"type":"string"},"image":{"type":"string","description":"Image URL."}}},"AbTest":{"type":"object","description":"A/B testing config (Advanced plan or above).","properties":{"enabled":{"type":"boolean"},"destinationB":{"type":"string","description":"Variant B destination URL."},"splitPercentage":{"type":"integer","default":50,"description":"Traffic percentage to destination A."}}},"RoutingRules":{"type":"object","description":"Traffic routing rules (Advanced+). Matched visitors are redirected to the rule URL instead of the base destination; rules override the A/B pick.","properties":{"geo":{"type":"object","description":"Uppercase ISO 3166-1 alpha-2 country code (or `default`) mapped to a destination URL. Max 20 rules.","additionalProperties":{"type":"string","format":"uri"},"example":{"IN":"https://example.com/in","US":"https://example.com/us"}},"continent":{"type":"object","description":"Continent code (AF, AN, AS, EU, NA, OC, SA) mapped to a URL. A country rule beats a continent rule.","additionalProperties":{"type":"string","format":"uri"},"example":{"EU":"https://example.com/eu","AS":"https://example.com/asia"}},"device":{"type":"object","description":"Device type mapped to a destination URL. Keys: `ios`, `android`, `mobile`, `desktop`. Specific OS beats the generic `mobile` bucket.","properties":{"ios":{"type":"string","format":"uri"},"android":{"type":"string","format":"uri"},"mobile":{"type":"string","format":"uri"},"desktop":{"type":"string","format":"uri"}}},"region":{"type":"object","description":"Lowercase \"region|cc\" key using the region name (e.g. \"karnataka|in\") or the ISO 3166-2 code (e.g. \"ka|in\") mapped to a URL. Beats country rules. Max 20 rules.","additionalProperties":{"type":"string","format":"uri"}},"city":{"type":"object","description":"Lowercase \"city|cc\" key (e.g. \"bengaluru|in\") mapped to a URL. Most specific; IP city detection is best-effort. Max 20 rules.","additionalProperties":{"type":"string","format":"uri"}}}},"Rotation":{"type":"object","description":"N-way weighted rotation (Advanced+). Visitors are split randomly across 2-10 target URLs by integer weights summing to 100. Supersedes the legacy abTest fields when set; analytics reports variants as A, B, C... by target order.","properties":{"targets":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"object","required":["url","weight"],"properties":{"url":{"type":"string","format":"uri"},"weight":{"type":"integer","minimum":1,"maximum":100}}}}}},"Pixel":{"type":"object","required":["provider","id"],"description":"A retargeting pixel fired from the redirect page for human visitors (crawlers never trigger pixels).","properties":{"provider":{"type":"string","enum":["meta","tiktok","gtm","google_ads","linkedin"]},"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$","description":"Vendor pixel/container/tag ID, e.g. a Meta Pixel ID. Google Tag Manager IDs must look like GTM-ABC123 and Google Ads tag IDs like AW-123456789. Links with a gtm pixel are created on u.gy (or your custom domain); a u2l.ai link cannot gain one on edit."}}},"QrStyle":{"type":"object","description":"QR styling (QR links only; pattern/logo customization requires Pro plan or above).","properties":{"dotsType":{"type":"string","default":"rounded"},"cornersSquareType":{"type":"string","default":"extra-rounded"},"cornersDotType":{"type":"string","default":"dot"},"logo":{"type":"string","description":"Logo image URL."}}},"CreateLinkRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Destination URL. `https://` is assumed if no scheme is given.","example":"https://example.com/my-page"},"domain":{"type":"string","default":"auto","description":"`auto` (default) picks the best domain for the destination. Or pass one of your domains from List domains."},"alias":{"type":"string","description":"Custom slug (5-50 chars, letters/numbers/hyphens/underscores). Random when omitted.","example":"my-link"},"title":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"password":{"type":"string","description":"Password-protect the link (Advanced plan or above)."},"expiresAt":{"type":"string","format":"date-time","description":"Expiration time (Advanced plan or above)."},"permanent":{"type":"boolean","default":true,"description":"true = 301 redirect, false = 302."},"folderId":{"type":"string"},"type":{"type":"string","enum":["SL","QR"],"default":"SL"},"customOg":{"$ref":"#/components/schemas/CustomOg"},"abTest":{"$ref":"#/components/schemas/AbTest"},"qrStyle":{"$ref":"#/components/schemas/QrStyle"},"cloaking":{"type":"boolean","description":"URL cloaking (Advanced+): serve the destination inside a frame so visitors keep seeing the short URL."},"deepLink":{"type":"boolean","description":"Open the destination in its native mobile app (YouTube, Instagram, etc.) when the visitor has it installed."},"routing":{"$ref":"#/components/schemas/RoutingRules"},"maxClicks":{"type":"integer","minimum":1,"description":"Expire the link after this many human clicks (Growth+). Enforcement is eventual: the link stops within a few minutes of the limit; bot traffic never counts."},"expiredUrl":{"type":"string","format":"uri","description":"Where visitors are redirected (302) once the link has expired by date or click limit. Unset shows the standard expired page."},"startsAt":{"type":"string","format":"date-time","description":"Before this time the link serves a not-active-yet page. Must be before expiresAt when both are set."},"rotation":{"$ref":"#/components/schemas/Rotation"},"pixels":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/Pixel"},"description":"Retargeting pixels (Advanced+). Fired from a brief redirect page for human visitors only."},"appFallback":{"type":"string","enum":["store"],"description":"App deep links (Advanced+): 'store' sends mobile visitors without the domain's app installed to the App Store / Play Store (with the link carried in the Play install referrer). Requires a mobile app configured on the link's domain in the dashboard; visitors with the app installed open it directly via the domain's universal/app links."}}},"UpdateLinkRequest":{"type":"object","description":"All fields optional - only sent fields are changed.","properties":{"destination":{"type":"string","description":"New destination URL."},"title":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"password":{"type":"string","description":"New password; send null to remove (Advanced plan or above)."},"expiresAt":{"type":"string","format":"date-time","description":"New expiration; send null to remove."},"permanent":{"type":"boolean"},"customOg":{"$ref":"#/components/schemas/CustomOg"},"abTest":{"$ref":"#/components/schemas/AbTest"},"cloaking":{"type":"boolean","description":"URL cloaking (Advanced+): serve the destination inside a frame so visitors keep seeing the short URL."},"deepLink":{"type":"boolean","description":"Open the destination in its native mobile app (YouTube, Instagram, etc.) when the visitor has it installed."},"routing":{"$ref":"#/components/schemas/RoutingRules"},"maxClicks":{"type":"integer","minimum":1,"description":"Expire the link after this many human clicks (Growth+). Enforcement is eventual: the link stops within a few minutes of the limit; bot traffic never counts."},"expiredUrl":{"type":"string","format":"uri","description":"Where visitors are redirected (302) once the link has expired by date or click limit. Unset shows the standard expired page."},"startsAt":{"type":"string","format":"date-time","description":"Before this time the link serves a not-active-yet page. Must be before expiresAt when both are set."},"rotation":{"$ref":"#/components/schemas/Rotation"},"pixels":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/Pixel"},"description":"Retargeting pixels (Advanced+). Fired from a brief redirect page for human visitors only."},"appFallback":{"type":"string","enum":["store"],"description":"App deep links (Advanced+): 'store' sends mobile visitors without the domain's app installed to the App Store / Play Store (with the link carried in the Play install referrer). Requires a mobile app configured on the link's domain in the dashboard; visitors with the app installed open it directly via the domain's universal/app links."}}},"CreateLinkResponse":{"type":"object","properties":{"id":{"type":"string"},"shortLink":{"type":"string","description":"The full short URL.","example":"https://u.gy/my-link"},"domain":{"type":"string"},"slug":{"type":"string"},"destination":{"type":"string"},"title":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"hasPassword":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"permanent":{"type":"boolean"},"type":{"type":"string","enum":["SL","QR"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"folderId":{"type":"string"},"clicks":{"type":"integer"},"noticeCode":{"type":"string","description":"Present when the domain differs from the one requested (trust-based placement)."},"notice":{"type":"string","description":"Human-readable explanation of the domain choice."},"requestedDomain":{"type":"string"}}},"Folder":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"linksCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"Domain":{"type":"object","properties":{"domain":{"type":"string","example":"u.gy"},"type":{"type":"string","enum":["default","trust-based","standard","subdomain","custom"],"description":"`default` is the automatic choice; `trust-based` (u2l.ai) accepts trusted destinations only."},"verified":{"type":"boolean"},"placement":{"type":"string","description":"Placement rule, when applicable (`automatic` or `trusted-only`)."}}},"LinkAnalytics":{"type":"object","properties":{"totalClicks":{"type":"integer","description":"All-time clicks."},"clicksInRange":{"type":"integer","description":"Clicks within timeRange."},"clicksToday":{"type":"integer"},"clicksThisWeek":{"type":"integer"},"timeRange":{"type":"integer","description":"The requested range in days (0 = all time)."},"countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string"},"clicks":{"type":"integer"}}}},"devices":{"type":"object","description":"Clicks per device type (mobile / desktop / tablet).","additionalProperties":{"type":"integer"}},"browsers":{"type":"array","items":{"type":"object","properties":{"browser":{"type":"string"},"clicks":{"type":"integer"}}}},"referrers":{"type":"array","items":{"type":"object","properties":{"referrer":{"type":"string"},"clicks":{"type":"integer"}}}},"os":{"type":"array","items":{"type":"object","properties":{"os":{"type":"string"},"clicks":{"type":"integer"}}}}}},"AccountStats":{"type":"object","properties":{"totalLinks":{"type":"integer"},"totalClicks":{"type":"integer"},"totalQRCodes":{"type":"integer"},"thisWeekClicks":{"type":"integer"},"plan":{"type":"string"},"limits":{"type":"object","properties":{"maxLinks":{"description":"Number, or the string `unlimited`.","oneOf":[{"type":"integer"},{"type":"string"}]},"maxQRCodes":{"oneOf":[{"type":"integer"},{"type":"string"}]},"apiRequestsPerMinute":{"type":"integer"},"apiRequestsPerDay":{"type":"integer"}}}}},"Account":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"plan":{"type":"string"},"billingCycle":{"type":"string","description":"monthly, yearly, or lifetime. Null on free plans."},"createdAt":{"type":"string","format":"date-time"},"usage":{"type":"object","properties":{"links":{"type":"integer"},"qrCodes":{"type":"integer"},"customDomains":{"type":"integer"},"apiKeysUsed":{"type":"integer"},"apiKeysLimit":{"type":"integer"}}}}},"Webhook":{"type":"object","properties":{"id":{"type":"string","example":"we_f9ae3c2a1ee24e1b9321e359c9830b96"},"url":{"type":"string","example":"https://example.com/u2l-webhook"},"events":{"type":"array","items":{"type":"string","enum":["link.created","link.updated","link.deleted"]}},"active":{"type":"boolean"},"failureCount":{"type":"integer","description":"Consecutive failed deliveries. The endpoint is disabled automatically at 20. Resets to 0 on any successful delivery."},"lastDeliveryAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}}},"CreateWebhookRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"HTTPS URL that receives event POSTs.","example":"https://example.com/u2l-webhook"},"events":{"type":"array","description":"Events to subscribe to. Defaults to all.","items":{"type":"string","enum":["link.created","link.updated","link.deleted"]}}}},"CreateWebhookResponse":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"Signing secret (u2lwh_...). Shown only in this response - store it now. Used to verify the X-U2L-Signature header.","example":"u2lwh_4f2axxxx91c3xxxx7e5bxx..."}}}]},"UpdateWebhookRequest":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["link.created","link.updated","link.deleted"]}},"active":{"type":"boolean","description":"Re-enabling a disabled endpoint resets its failure count."}}}}}}