{"success":true,"message":"API Documentation","data":{"version":"1.0.0","name":"LNK7 API","description":"API for managing short links and reading analytics","base_url":"https:\/\/app.lnk7.io\/api\/v1","authentication":{"type":"Bearer Token","header":"Authorization: Bearer {token}","description":"Get your API token from your account settings","url":"https:\/\/app.lnk7.io\/settings\/api-tokens","abilities":{"read":"GET endpoints, plus the POST \/analytics\/export\/* endpoints (they only read)","write":"POST \/links, PUT|PATCH \/links\/{id}, and bulk activate\/deactivate","delete":"DELETE \/links\/{id} and bulk delete"},"verified_email_required":true},"rate_limits":{"authenticated":"60 requests per minute, per account (all of your tokens share the allowance). Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 adds X-RateLimit-Reset and Retry-After.","unauthenticated":"30 requests per minute, per IP (this documentation endpoint)"},"endpoints":{"links":{"GET \/links":"List short links in your organization (paginated)","POST \/links":"Create a new short link (201 Created)","GET \/links\/{id}":"Get a specific short link","PUT \/links\/{id}":"Update a short link (partial; identical to PATCH \u2014 omitted fields are left unchanged, not cleared)","PATCH \/links\/{id}":"Update a short link (same body as PUT; every field optional)","DELETE \/links\/{id}":"Delete a short link (204 No Content)","POST \/links\/bulk":"Activate, deactivate or delete up to 100 links in one call","GET \/links\/{id}\/analytics":"Get per-link analytics"},"analytics":{"GET \/analytics\/overview":"Total links, plus human (non-bot) visits in a rolling window: by default the 90-day retention window (the same window as each link's visits_count); ?days=30 gives the dashboard's \"last 30 days\". The response's window object gives the exact start, end and timezone.","GET \/links\/{id}\/analytics":"Per-link analytics (accepts ?days=). Dates in visits_over_time are the organization display timezone (Europe\/London), one entry per day, zero-filled.","GET \/analytics\/export\/options":"Available export types, formats and date ranges","POST \/analytics\/export\/preview":"Preview the rows an export would contain","POST \/analytics\/export\/global":"Export analytics across your organization as CSV","POST \/analytics\/export\/short-links\/{id}":"Export one link's analytics as CSV"},"user":{"GET \/user":"Get current user information"}},"request_fields":{"POST \/links, PUT|PATCH \/links\/{id}":{"destination_url":"string, required on create. http\/https only, max 2048 characters. Checked against the URL safety service.","title":"string, optional, max 255 characters.","custom_slug":"string, optional, max 255 characters, [A-Za-z0-9-_] only. A new or changed custom slug is stored lowercase and must be unique ignoring case; reserved slugs are rejected. Sending the link's current slug back in any casing (e.g. a GET \u2192 PUT round trip) leaves it unchanged. Omit to have one generated (generated slugs are mixed case).","expires_at":"ISO 8601 date-time, optional. A new or changed expiry must be in the future; the stored value sent back unchanged is accepted even if it has passed. Send null to remove it.","password":"string, optional, max 255 characters. Send null or \"\" to remove an existing password. Never returned \u2014 read has_password instead.","is_active":"boolean, optional. false disables the link (it stops redirecting).","click_limit":"integer 1-2000000000, optional. The link stops redirecting after this many counted clicks (status becomes expired). Send null to remove the limit."},"POST \/links\/bulk":{"action":"string, required: activate | deactivate | delete.","link_ids":"array of link ids, required, 1-100 items. Ids you cannot act on (unknown, another organization, or barred by your role) come back under data.skipped."},"POST \/analytics\/export\/global, POST \/analytics\/export\/short-links\/{id}":{"type":"string, required: visits | geographic | devices | referrers | performance (global also accepts overview).","format":"string, required: csv (the only format).","start_date":"date (YYYY-MM-DD), optional, a day in your organization's display timezone (Europe\/London unless set). Without dates the export covers the last 30 days; clamped to the 90-day retention window.","end_date":"date (YYYY-MM-DD), optional, on or after start_date, inclusive. Defaults to today.","row_limit":"type=visits writes one row per visit, up to 100,000 rows. A larger export is refused with a 422 asking you to narrow start_date\/end_date \u2014 a file is never silently truncated. Daily rows (type=overview) are local days in the display timezone; visited_at (type=visits) is UTC, formatted YYYY-MM-DD HH:MM:SS."},"POST \/analytics\/export\/preview":{"type":"string, required, as above.","resource_type":"string, optional: short-link, with resource_id set to a link id, to preview one link's export.","resource_id":"link id (UUID), optional.","start_date":"as above.","end_date":"as above."}},"query_parameters":{"GET \/links":{"search":"string, matches title, slug and destination URL.","status":"active | inactive. active = the link redirects right now (not disabled, flagged, expired or at its click limit), exactly the links whose status is active. Any other value is a 422.","sort_by":"created_at | updated_at | visits_count | title (default created_at).","sort_order":"asc | desc (default desc).","per_page":"integer, clamped to 1-100 (default 15)."},"GET \/links\/{id}\/analytics":{"days":"integer 1-90 (default 30). A rolling window back from now that bounds every metric in the response; 90 days is the analytics retention window."},"GET \/analytics\/overview":{"days":"integer 1-90, optional. The rolling window total_visits covers (returned as data.window). Omit it for the whole 90-day retention window."}},"link_object":{"id":"string (UUID) \u2014 the id used in every \/links\/{id} path","short_url":"string \u2014 the full https short URL","destination_url":"string","custom_slug":"string","title":"string|null","is_active":"boolean","status":"active | disabled | flagged | expired","has_password":"boolean","expires_at":"ISO 8601 date-time|null","click_limit":"integer|null \u2014 null means no limit","visits_count":"integer \u2014 human (non-bot) visits inside the 90-day retention window, the same number the dashboard shows","created_at":"ISO 8601 date-time","updated_at":"ISO 8601 date-time"},"response_format":{"success_response":{"success":true,"message":"Success message","data":"Response data"},"error_response":{"success":false,"message":"Error message","errors":"Validation errors (optional)","note":"Every error, including 500, uses this shape. CSV export endpoints stream text\/csv on success and use this envelope on error."},"paginated_response":{"success":true,"message":"Success message","data":"Array of items","pagination":{"current_page":1,"last_page":10,"per_page":15,"total":150,"from":1,"to":15,"has_more_pages":true}}},"status_codes":{"200":"OK - Request successful","201":"Created - Resource created successfully","204":"No Content - Resource deleted successfully","400":"Bad Request - Invalid request data","401":"Unauthorized - Invalid or missing authentication","403":"Forbidden - Token is missing the required ability, or your role does not allow this action","404":"Not Found - Resource not found, or it belongs to another organization","409":"Conflict - This account has no active organization; create or join one before using the API","422":"Unprocessable Entity - Validation errors","429":"Too Many Requests - Rate limit exceeded","500":"Internal Server Error - Server error"}}}