Links
Short links are the core object of the API. A link points a slug on one of your domains at a destination URL, and can carry UTM parameters, tags, an expiry date, social preview metadata, and retargeting pixels.
| Method | Path | Scope |
|---|---|---|
GET | /v1/links | links:read |
POST | /v1/links | links:write |
GET | /v1/links/{id} | links:read |
PATCH | /v1/links/{id} | links:write |
DELETE | /v1/links/{id} | links:write |
Links also carry QR codes and click analytics on nested routes.
The link object
{
"id": "9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40",
"title": "Summer Sale",
"destination_url": "https://example.com/summer-sale?utm_source=newsletter",
"short_url": "go.acme.com/summer",
"slug": "summer",
"hostname": "go.acme.com",
"og_title": "Summer Sale — 30% off everything",
"og_description": "Our biggest sale of the year, ending Sunday.",
"og_image": "https://cdn.example.com/preview.png",
"utm_params": { "utm_source": "newsletter" },
"tags": ["q3", "email"],
"expires_at": "2026-12-31T00:00:00.000000Z",
"pixel_ids": ["3f9c8a71-2b4d-4e60-8a15-9d7c6b3e21f8"],
"clicks": 128,
"status": "active",
"created_at": "2026-08-28T10:14:02.000000Z",
"updated_at": "2026-08-28T11:02:44.000000Z"
}| Field | Type | Description |
|---|---|---|
id | uuid | The link’s identifier |
title | string | The link’s title. Scraped from the destination page unless you set it yourself. |
destination_url | string | Where the link redirects to, including any UTM parameters that were applied |
short_url | string | The full short link, hostname and slug combined |
slug | string | The path portion of the short link |
hostname | string | The domain the link lives on. It can be your custom domain, or the shared Link Squeeze domain |
og_title | string | null | Title shown when the short link is shared on social media |
og_description | string | null | Description shown when the short link is shared |
og_image | string | null | Absolute URL of the preview image |
utm_params | object | The UTM parameters applied to the destination URL |
tags | array of strings | Your own labels for the link |
expires_at | timestamp | null | When the link stops resolving; null means it never expires |
pixel_ids | array of uuids | Retargeting pixels that fire when the link is clicked |
clicks | integer | Total clicks recorded for the link |
status | string | active or inactive |
created_at | timestamp | When the link was created |
updated_at | timestamp | When the link was last modified |
hostname is always a hostname. A link on the shared Link Squeeze domain
reports that domain (sqzly.link), not an absent value. To tell the two
apart, compare hostname against the domains returned by GET /v1/domains.
List links
GET /v1/linksScope: links:read
Returns your links, newest first, in the standard paginated shape.
| Query parameter | Type | Description |
|---|---|---|
page | integer | Page to return. Defaults to 1. |
per_page | integer | Results per page. Defaults to 15, maximum 100. |
curl "https://app.linksqueeze.io/api/v1/links?per_page=50" \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"Create a link
POST /v1/linksScope: links:write
| Field | Type | Required | Description |
|---|---|---|---|
destination_url | string | Yes | The URL to redirect to. Must be a valid, publicly reachable URL, maximum 2048 characters. |
slug | string | No | The short link path. Letters, numbers, dashes, and underscores only, maximum 255 characters. Must be globally unique. Generated for you if omitted. |
title | string | No | The link’s title, maximum 255 characters. Scraped from the destination if omitted. |
hostname | string | No | A custom domain registered to your account. Defaults to the shared Link Squeeze domain. |
og_title | string | No | Social preview title, maximum 255 characters. Scraped if omitted. |
og_description | string | No | Social preview description, maximum 5000 characters. Scraped if omitted. |
og_image | string | No | Absolute URL of a preview image you already host, maximum 2048 characters. |
og_image_file | file | No | Upload a preview image instead. jpg, jpeg, png, webp, or gif, maximum 2 MB. Multipart requests only. |
utm_params | object | No | Any of utm_source, utm_medium, utm_campaign, utm_term, utm_content. Each maximum 255 characters. |
tags | array of strings | No | Your own labels. Each maximum 255 characters. |
expires_at | timestamp | No | When the link should stop resolving. Must be in the future. |
pixel_ids | array of uuids | No | Retargeting pixels to attach. Each must be a pixel registered to your account. |
Returns 201 Created with the link object.
curl -X POST https://app.linksqueeze.io/api/v1/links \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"destination_url": "https://example.com/summer-sale",
"slug": "summer",
"hostname": "go.acme.com",
"utm_params": {
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "summer-2026"
},
"tags": ["q3", "email"],
"expires_at": "2026-12-31T00:00:00Z",
"pixel_ids": ["3f9c8a71-2b4d-4e60-8a15-9d7c6b3e21f8"]
}'Destination URLs are checked before the link is created
Creating a link runs safety checks before the link is created. A destination is rejected with 422 on destination_url when it:
- is not publicly routable — a loopback, private, or link-local address cannot work for the people clicking the link, and pointing our preview scraper at it on your behalf would be a request-forgery vector;
- cannot be reached —
The destination URL could not be reached. Check the URL and try again. - is flagged as unsafe —
The destination URL was flagged as unsafe and cannot be shortened.
These checks run on POST, and on any PATCH that changes destination_url.
Preview metadata is scraped for you
On creation the destination page is scraped for its title and Open Graph tags, and anything you supplied explicitly wins over what was found — per field. Sending only og_title keeps the scraped og_description and og_image.
If the page cannot be scraped, title falls back to "Untitled" and the OG fields are left null.
To set a preview image you can either point at one you already host with og_image, or upload one with og_image_file as a multipart request. When both are sent, the upload wins. Uploaded images are returned from og_image as an absolute URL.
curl -X POST https://app.linksqueeze.io/api/v1/links \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json" \
-F "destination_url=https://example.com/summer-sale" \
-F "og_title=Summer Sale" \
-F "og_image_file=@preview.png"How domains are referenced
Links reference a domain by its hostname, not by its domain UUID, so you can create a link on go.acme.com without first looking up the domain’s id.
The hostname must be a domain registered to your account. One that is not — including a domain owned by another account — is rejected with 422:
{
"error": {
"code": "validation_error",
"message": "The given data was invalid.",
"errors": {
"hostname": ["This hostname is not registered to your account."]
}
}
}Omit hostname (or send null) to put the link on the shared Link Squeeze short domain.
How UTM parameters are applied
UTM parameters are baked into the destination URL at write time. Creating a link with destination_url of https://example.com/product and utm_source of newsletter returns:
{
"destination_url": "https://example.com/product/?utm_source=newsletter",
"utm_params": { "utm_source": "newsletter" }
}Retrieve a link
GET /v1/links/{id}Scope: links:read
curl https://app.linksqueeze.io/api/v1/links/9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40 \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"A link belonging to another account returns 404 not_found, the same as a link that does not exist.
Update a link
PATCH /v1/links/{id}Scope: links:write
Every field is optional. Fields you do not send are left untouched.
| Field | Type | Description |
|---|---|---|
destination_url | string | New destination URL. Required when sending utm_params. Re-runs the safety checks. |
slug | string | New slug. Must be globally unique. |
title | string | null | New title. |
hostname | string | null | Move the link to another of your domains, or send null to move it back to the shared domain. |
og_title | string | null | New social preview title, or null to clear it. |
og_description | string | null | New social preview description, or null to clear it. |
og_image | string | null | New preview image URL, or null to clear it. |
og_image_file | file | Upload a replacement preview image. Multipart requests only. |
utm_params | object | null | Replaces the existing UTM parameters. Must be sent together with destination_url. |
tags | array of strings | null | Replaces the existing tags. |
expires_at | timestamp | null | New expiry, or null to remove it. Must be in the future. |
pixel_ids | array of uuids | null | Replaces the attached pixels. Send [] to detach all of them. |
curl -X PATCH https://app.linksqueeze.io/api/v1/links/9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40 \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "tags": ["rebrand"] }'Returns 200 OK with the updated link object.
utm_params cannot be updated on its own. Because UTM parameters are
baked into the stored destination URL and no separate clean URL is kept,
re-applying them to an already-tagged URL would risk duplicating query
parameters. Send destination_url alongside utm_params — a
utm_params-only patch is rejected with 422.
Updating preview metadata
The OG fields can be patched on their own, without resending the destination URL. Each behaves independently:
- omitted — left as it is;
- sent with a value — replaced;
- sent as
null— cleared.
Changing destination_url does not re-scrape or blank the preview metadata — a link you have styled by hand keeps its metadata when its destination moves. Send the OG fields explicitly to change them.
Uploading a new og_image_file replaces the previous image, and the old one is deleted if we were hosting it.
Array fields are replaced, not merged
tags, pixel_ids, and utm_params are replaced wholesale by the value you send. To add one tag to a link that already has two, send all three.
Fields you omit entirely are preserved. Changing destination_url without mentioning pixel_ids, for example, keeps the link’s existing pixels attached.
Clearing a hostname
Send "hostname": null to move a link off a custom domain and back onto the shared Link Squeeze domain. The response then reports the shared domain in hostname and short_url.
Delete a link
DELETE /v1/links/{id}Scope: links:write
Returns 204 No Content with an empty body.
curl -X DELETE https://app.linksqueeze.io/api/v1/links/9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40 \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"Deleting a link stops it resolving immediately. Anyone who has already shared or printed the short link — or scanned its QR code — will get your domain’s not-found page from that point on.
Events
Creating a link through the API fires the link.created webhook event, exactly once, with the complete link record. See Webhooks.