Skip to content

Links API

Base URL: https://qck.sh/public-api/v1

All endpoints require an X-API-Key header. See Authentication.


GET /links
ParamTypeDefaultDescription
pageinteger1Page number
per_pageinteger20Items per page (max 250)
searchstringNoneSearch URL, title, short code
tagsstring[]NoneFilter by tags
is_activebooleanNoneFilter active/inactive
has_passwordbooleanNoneFilter password-protected links
domainstringNoneFilter by domain name
domain_idstringNoneFilter by custom domain ID
created_afterstringNoneCreated after this date (ISO 8601)
created_beforestringNoneCreated before this date (ISO 8601)
last_active_afterstringNoneLast clicked after this date (ISO 8601)
sort_bystringcreated_atSort field
sort_orderstringdescasc 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
}
}

POST /links
FieldTypeRequiredDescription
urlstringYesDestination URL
titlestringNoOG title (auto-extracted if omitted)
descriptionstringNoOG description (auto-extracted if omitted)
og_imagestringNoOG image URL (auto-extracted if omitted)
custom_aliasstringNoCustom short code (3+ chars, Basic+)
tagsstring[]NoTags for organization
expires_atstringNoISO 8601 expiration
is_password_protectedbooleanNoMust be true for password to take effect
passwordstringNoPassword-protect the link (requires is_password_protected: true, otherwise silently ignored)
utm_sourcestringNoUTM source
utm_mediumstringNoUTM medium
utm_campaignstringNoUTM campaign
utm_termstringNoUTM term
utm_contentstringNoUTM content
domain_idstringNoCustom domain ID
campaign_idstringNoCampaign 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 /links/{id}

Response 200: same shape as create response.

Permission: links:read


PATCH /links/{id}

All fields optional. Destination URL cannot be changed. Create a new link instead.

FieldTypeDescription
custom_aliasstringNew short code
titlestringNew title
descriptionstringNew description
og_imagestringNew OG image URL
tagsstring[]Replace tags
expires_atstring | nullNew expiration or null to remove
is_activebooleanEnable/disable
is_password_protectedbooleanMust be true for password to take effect; false clears the password
passwordstringNew password (only applied when is_password_protected is true)
campaign_idstring | nullAssign or remove campaign
utm_sourcestringUTM source (merged; empty string clears)
utm_mediumstringUTM medium (merged; empty string clears)
utm_campaignstringUTM campaign (merged; empty string clears)
utm_termstringUTM term (merged; empty string clears)
utm_contentstringUTM 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 /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


POST /links/bulk

Body 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:

StatusMeaning
201All links created
207Partial success (success: false, check failed[])
422All links failed
408REQUEST_TIMEOUT: batch exceeded the 60-second processing cap

Permission: links:write


GET /links/{id}/stats

Response 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


PUT /links/{id}/og-image

Send 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 /links/{id}/og-image

Response 200

Permission: links:write