Links API
Base URL: https://qck.sh/public-api/v1
All endpoints require an X-API-Key header. See Authentication.
List links
Section titled “List links”GET /links| Param | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 20 | Items per page (max 250) |
search | string | None | Search URL, title, short code |
tags | string[] | None | Filter by tags |
is_active | boolean | None | Filter active/inactive |
has_password | boolean | None | Filter password-protected links |
domain | string | None | Filter by domain name |
domain_id | string | None | Filter by custom domain ID |
created_after | string | None | Created after this date (ISO 8601) |
created_before | string | None | Created before this date (ISO 8601) |
last_active_after | string | None | Last clicked after this date (ISO 8601) |
sort_by | string | created_at | Sort field |
sort_order | string | desc | asc or desc |
Response 200: data is the array of links; pagination lives in meta:
{ "success": true, "data": [{ "id": "...", "short_url": "...", "original_url": "..." }], "meta": { "request_id": "0190a8b2-...", "timestamp": "2026-04-08T12:00:00Z", "page": 1, "per_page": 20, "total": 100, "total_pages": 5 }}Create a link
Section titled “Create a link”POST /links| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Destination URL |
title | string | No | OG title (auto-extracted if omitted) |
description | string | No | OG description (auto-extracted if omitted) |
og_image | string | No | OG image URL (auto-extracted if omitted) |
custom_alias | string | No | Custom short code (3+ chars, Basic+) |
tags | string[] | No | Tags for organization |
expires_at | string | No | ISO 8601 expiration |
is_password_protected | boolean | No | Must be true for password to take effect |
password | string | No | Password-protect the link (requires is_password_protected: true, otherwise silently ignored) |
utm_source | string | No | UTM source |
utm_medium | string | No | UTM medium |
utm_campaign | string | No | UTM campaign |
utm_term | string | No | UTM term |
utm_content | string | No | UTM content |
domain_id | string | No | Custom domain ID |
campaign_id | string | No | Campaign ID (Growth+) |
Response 201
{ "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "short_code": "abc123", "short_url": "https://qck.sh/abc123", "original_url": "https://example.com", "title": "Example", "description": "...", "tags": [], "is_active": true, "created_at": "2026-04-08T12:00:00Z" }}Permission: links:write
Get a link
Section titled “Get a link”GET /links/{id}Response 200: same shape as create response.
Permission: links:read
Update a link
Section titled “Update a link”PATCH /links/{id}All fields optional. Destination URL cannot be changed. Create a new link instead.
| Field | Type | Description |
|---|---|---|
custom_alias | string | New short code |
title | string | New title |
description | string | New description |
og_image | string | New OG image URL |
tags | string[] | Replace tags |
expires_at | string | null | New expiration or null to remove |
is_active | boolean | Enable/disable |
is_password_protected | boolean | Must be true for password to take effect; false clears the password |
password | string | New password (only applied when is_password_protected is true) |
campaign_id | string | null | Assign or remove campaign |
utm_source | string | UTM source (merged; empty string clears) |
utm_medium | string | UTM medium (merged; empty string clears) |
utm_campaign | string | UTM campaign (merged; empty string clears) |
utm_term | string | UTM term (merged; empty string clears) |
utm_content | string | UTM content (merged; empty string clears) |
UTM fields use merge semantics: omitted fields keep their current value, a new value overrides, and an empty string ("") clears the parameter.
Permission: links:write
Delete a link
Section titled “Delete a link”DELETE /links/{id}Soft delete: the link stops resolving immediately but is recoverable for about 7 days before permanent removal.
Response 200
{ "success": true, "data": { "message": "Link deleted successfully" } }Permission: links:delete
Bulk create
Section titled “Bulk create”POST /links/bulkBody is an array of create link objects. Max batch size depends on tier. The operation supports partial success.
Response: a BulkCreateResult object:
{ "success": true, "data": { "created": [ { "index": 0, "link": { "id": "...", "short_url": "...", "original_url": "..." } } ], "failed": [ { "index": 1, "url": "https://bad-url", "error": "Invalid URL format", "error_type": "validation" } ], "total_requested": 2, "success_count": 1, "failure_count": 1 }}Status codes:
| Status | Meaning |
|---|---|
201 | All links created |
207 | Partial success (success: false, check failed[]) |
422 | All links failed |
408 | REQUEST_TIMEOUT: batch exceeded the 60-second processing cap |
Permission: links:write
Get link stats
Section titled “Get link stats”GET /links/{id}/statsResponse 200
{ "success": true, "data": { "short_code": "abc123", "original_url": "https://example.com", "total_clicks": 1234, "unique_visitors": 890, "bot_clicks": 45, "human_clicks": 1189, "last_accessed_at": "2026-04-08T10:00:00Z", "is_active": true, "days_active": 30, "average_clicks_per_day": 41.1, "conversion_rate": 0.72 }}Permission: analytics:read
Upload OG image
Section titled “Upload OG image”PUT /links/{id}/og-imageSend raw image bytes with Content-Type header (image/png, image/jpeg, image/webp, image/gif). Max 5 MB. Images are resized to 1200x630 and converted to WebP.
Requires: Growth ($49/mo) or higher.
Response 200
{ "success": true, "data": { "og_image": "https://cdn.qck.sh/og/550e8400.webp", "message": "OG image uploaded successfully" }}Permission: links:write
Delete OG image
Section titled “Delete OG image”DELETE /links/{id}/og-imageResponse 200
Permission: links:write