Skip to Content

Endpoints

All endpoints share the base path:

https://api.codelloy.com/link/external/v1

Authentication is by X-API-KEY on every request. See Obtaining Your API Key to generate one and Mobile Apps for the deep-link setup that the schema below references via mobileAppId.

Every response is wrapped in the standard envelope { response: …, errors: … }. On success, response is the payload and errors is omitted; on failure, response is omitted and errors is a non-empty array. See API Errors for the full error catalogue.


Create or Update a Short URL

Creates a new short URL or updates an existing one (matched by shortCode).

POST /link/external/v1/shortenedUrl/save

Request Headers

HeaderValueRequired
X-API-KEYYour API keyYes
Content-Typeapplication/jsonYes

Request Body

FieldTypeRequiredDescription
urlstringYesDestination URL. Must be non-empty.
shortCodestringNoCustom alias for the short link. 7-50 characters, alphanumeric / hyphen / underscore only (regex ^[a-zA-Z0-9_-]{7,50}$). Auto-generated when omitted. Re-using an existing shortCode belonging to your organisation updates that record.
shortLinkNamestringNoHuman-readable label shown in the dashboard list view. Strongly recommended for API-created links so operators can find them.
customDomainstringNoOne of your organisation’s active custom domains to serve this link on (e.g. go.yourbrand.com). Omit to use your organisation’s primary domain. A value that isn’t an active domain of your org is rejected with 400 (VE001). The response smartLink reflects the resulting host. Set up and verify the domain on the dashboard first — see Custom Domains.
isActivebooleanNoDefaults to true. Set to false to archive the link without deleting it (clients get HTTP 410 EN002 at click time).
cloakEnabledbooleanNofalse on create. On an update, omitting it leaves the stored value alone — it does not reset to false, so send false explicitly to un-cloak a link. When true, the destination is rendered inside a full-viewport frame instead of being navigated to, so your short-link host stays in the visitor’s address bar for the whole visit. Accepted on any link. See the cloaking note below for what it does not do.
utmSourcestringNoUTM source tag (appended to the destination URL at click time).
utmCampaignstringNoUTM campaign tag.
utmMediumstringNoUTM medium tag.
enableCustomMetadatabooleanNoWhen true, the customMetadata* fields below override the destination URL’s Open Graph preview for social-sharing surfaces. Requires the Metadata Preview feature on your plan.
customMetadataTitlestringNoOverride title for link previews.
customMetadataDescriptionstringNoOverride description for link previews.
customMetadataImageUrlstringNoOverride image URL for link previews.
deepLinksarrayNoArray of DeepLink objects (see sub-table below). Omit or pass [] for a plain redirect-only short URL.

DeepLink Entry Schema

Each entry in the deepLinks[] array follows this shape.

FieldTypeDirectionDescription
mobileAppIdstringRequired on requestThe 12-character ID from your Mobile apps page. Identifies the registered MobileApp this entry targets. Null/blank/unknown values return DL001.
deeplinkUrlstringRequestCustom-scheme URI used when the app is installed (e.g. myapp://product/123).
fallbackUrlstringRequestWeb URL used when fallbackEnabled=true and the app is not installed.
linkBehaviourstringRequest"app" (try the deep link first) or "webBrowser" (skip the app and go straight to url).
fallbackEnabledbooleanRequestWhen true, prefer fallbackUrl over the registered Store URL on app-not-installed. When false, prefer the Store URL.
platformstringResponse only (server-controlled)Reflects the registered MobileApp.platform ("IOS" / "ANDROID" / "WEB"). Values supplied in the request body are silently overwritten on save.
storeUrlstringResponse only (server-controlled)Reflects the registered MobileApp.storeUrl at read time. Edits on the Mobile apps page are picked up immediately on the next read.

Example Request

curl -X POST https://api.codelloy.com/link/external/v1/shortenedUrl/save \ -H "X-API-KEY: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/product/123", "shortCode": "spring-sale", "shortLinkName": "Spring sale — product 123", "customDomain": "go.yourbrand.com", "deepLinks": [ { "mobileAppId": "abc123def456", "deeplinkUrl": "myapp://product/123", "fallbackUrl": "https://example.com/product/123", "linkBehaviour": "app", "fallbackEnabled": true }, { "mobileAppId": "789xyz456ghi", "deeplinkUrl": "myapp://product/123", "fallbackUrl": "https://example.com/product/123", "linkBehaviour": "app", "fallbackEnabled": true } ] }'

Success Response

HTTP/1.1 200 OK Content-Type: application/json
{ "response": { "url": "https://example.com/product/123", "shortCode": "spring-sale", "shortLinkName": "Spring sale — product 123", "smartLink": "https://go.yourbrand.com/spring-sale", "isActive": true, "cloakEnabled": false, "deepLinks": [ { "mobileAppId": "abc123def456", "platform": "IOS", "storeUrl": "https://apps.apple.com/app/myapp/id123456789", "deeplinkUrl": "myapp://product/123", "fallbackUrl": "https://example.com/product/123", "linkBehaviour": "app", "fallbackEnabled": true, "appId": 42 }, { "mobileAppId": "789xyz456ghi", "platform": "ANDROID", "storeUrl": "https://play.google.com/store/apps/details?id=com.example.myapp", "deeplinkUrl": "myapp://product/123", "fallbackUrl": "https://example.com/product/123", "linkBehaviour": "app", "fallbackEnabled": true, "appId": 43 } ], "createdAt": "2026-05-15T10:30:00Z", "updatedAt": "2026-05-15T10:30:00Z", "createdBy": "api-key:Production Integration", "updatedBy": "api-key:Production Integration", "createdVia": "EXTERNAL", "lastModifiedVia": "EXTERNAL" } }

Custom domains: smartLink reflects your organisation’s active custom domain when the link is created (e.g. https://go.yourbrand.com/spring-sale). If the custom domain is later removed, https://re.codelloy.com/{shortCode} continues to resolve as a fallback.

Audit attribution: createdBy and updatedBy start with api-key: for any write performed through the External API, followed by the (required) label of the key that made the request. See Obtaining Your API Key for the labelling flow.

cloakEnabled keeps your short-link host in the visitor’s address bar by rendering the destination in a full-viewport frame instead of navigating to it. Several things to know before you switch it on.

It is branding, not concealment. The destination URL stays visible in the page source, in the browser’s network tab, and in link previews. Do not present a cloaked link to your own users as private or hidden.

The API does not check whether your destination can be framed. There is no error to alert you.

Many sites refuse to be framed by anyone. They send X-Frame-Options: DENY or SAMEORIGIN, or a CSP frame-ancestors directive that excludes your domain. YouTube, most banks and many SaaS login pages all do.

We never check this when you save a link. A cloaked link to a destination that refuses framing is created exactly as you asked, and returns 200:

POST /link/external/v1/shortenedUrl/save → HTTP 200 (response abridged):

{ "response": { "shortCode": "fca66b8", "url": "https://www.youtube.com/watch?v=...", "cloakEnabled": true } }

cloakEnabled stays true. It is not silently downgraded to false, and no warning is returned — not in the body, not in a header.

What your visitors get is a blank page. Not an error page, not a redirect, not a 4xx. The short link returns HTTP 200, the frame is created at full size pointing at the right destination, and nothing renders inside it. If you are ever handed a bug report that says “the link is broken, but it loads fine”, this is almost certainly the cause.

Opening the link in a browser is the only check that exists. Do it before you roll cloaking out:

# 1. Read the destination's framing headers. Use GET, not HEAD (-I): a meaningful # minority of servers answer HEAD differently from the request a browser makes. curl -sS -D - -o /dev/null "https://example.com/landing" \ | grep -i "^x-frame-options:\|frame-ancestors" # 2. Open the returned smartLink in a browser and confirm you see the page, not a blank pane

Read the output as a browser would, not as a keyword match:

  • X-Frame-Options: DENY or SAMEORIGIN — it will not frame.
  • A CSP frame-ancestors directive that does not name your short-link host — it will not frame. Note the host that matters is the one the link is served on: your custom domain if the link is branded, re.codelloy.com if it is not.
  • A CSP with no frame-ancestors directive does not block framing at all. Nearly every modern site sends some Content-Security-Policy; default-src, script-src and the rest say nothing about who may embed the page. Grepping for the header name alone will tell you almost every destination refuses, which is wrong.
  • No relevant header — it will frame, today. See “Nothing re-checks after the fact” below.

Nothing re-checks after the fact. A destination that frames happily today can add X-Frame-Options at any time, and every cloaked link pointing at it goes blank from that moment on. No notification is sent, because the change happens on their servers, not ours. If you cloak links to destinations you do not control, re-test them periodically.

The Codelloy dashboard behaves differently, deliberately. Links created in the dashboard are probed first, and the save is blocked if the destination definitely refuses framing. The API has no such guardrail on purpose: probing would make every write depend on a third party’s uptime and add seconds of latency to your integration. So a link the dashboard refuses to create is one the API will create without comment.

It is returned on every read. "cloakEnabled": false comes back on every link you read, including links created before cloaking existed and links you never set the field on — a link created without the field is stored as false. See the Field Reference for the canonical row.

You cannot un-cloak a link by omitting the field. save is an upsert, and on the update half an omitted field leaves the stored value untouched — that is what makes it safe to send a partial body. So a link stored with cloakEnabled: true stays cloaked no matter how many times you re-POST it without the field, and no error tells you the request did nothing. Send "cloakEnabled": false explicitly.

Validation Errors

A missing required field returns HTTP 400. Bean Validation errors emit SML002; ad-hoc validation (e.g. invalid shortCode format) emits VE001. Branch on the code field, not the HTTP status. A rejected shortCode also carries details.field (always shortCode) and a details.reason of FORMAT, RESERVED or TAKEN — the status, code and message are unchanged, so existing branching keeps working.

HTTP/1.1 400 Bad Request Content-Type: application/json
{ "errors": [ { "code": "SML002", "message": "must not be empty" } ] }

A deepLinks[] entry with a missing or unknown mobileAppId returns HTTP 400 with DL001:

{ "errors": [ { "code": "DL001", "message": "Deep link entry rejected: mobileAppId 'unknown123' is not recognised for this organisation", "details": { "kind": "deep_link_validation", "mobileAppId": "unknown123" } } ] }

Bulk Create or Update Short URLs

Creates or updates up to 30 short URLs in a single request.

POST /link/external/v1/shortenedUrl/bulkSave

Request Headers

HeaderValueRequired
X-API-KEYYour API keyYes
Content-Typeapplication/jsonYes

Request Body

FieldTypeRequiredDescription
dataarrayYesArray of short-URL objects (same fields as the single save endpoint). Max 30 items.

Example Request

curl -X POST https://api.codelloy.com/link/external/v1/shortenedUrl/bulkSave \ -H "X-API-KEY: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "data": [ { "url": "https://example.com/page-one", "shortCode": "page-one", "shortLinkName": "Page One" }, { "url": "https://example.com/page-two", "shortLinkName": "Page Two" } ] }'

Success Response

A single envelope wraps two arrays: successRecords (saved) and failureRecords (rejected), plus a bulkSaveSummary count.

HTTP/1.1 200 OK Content-Type: application/json
{ "response": { "successRecords": [ { "shortCode": "page-one", "url": "https://example.com/page-one", "shortLinkName": "Page One", "smartLink": "https://re.codelloy.com/page-one", "isActive": true, "createdVia": "EXTERNAL", "lastModifiedVia": "EXTERNAL" }, { "shortCode": "a81bb82", "url": "https://example.com/page-two", "shortLinkName": "Page Two", "smartLink": "https://re.codelloy.com/a81bb82", "isActive": true, "createdVia": "EXTERNAL", "lastModifiedVia": "EXTERNAL" } ], "failureRecords": [], "bulkSaveSummary": { "successCount": 2, "failureCount": 0 } } }

Whole-batch validation failure

If the outer payload is malformed (e.g. data missing or empty), the entire request fails with the standard errors[] envelope; no per-record processing happens.

{ "errors": [ { "code": "SML002", "message": "must not be empty" } ] }

List Short URLs

Returns a paginated list of your short URLs. All filtering is via the query DSL parameter (described below).

GET /link/external/v1/shortenedUrl?page={page}&size={size}&query={query}

Request Headers

HeaderValueRequired
X-API-KEYYour API keyYes

Query Parameters

ParameterTypeDefaultDescription
pageinteger0Page number (zero-based).
sizeinteger20Number of results per page.
querystring—Filter expression in the Codelloy query DSL (see below).

Example Request

curl -X GET "https://api.codelloy.com/link/external/v1/shortenedUrl?page=0&size=10&query=isActive=true" \ -H "X-API-KEY: your_api_key_here"

Success Response

The response envelope wraps a data[] array of short-URL objects plus a page metadata object with size / number / totalElements / totalPages.

HTTP/1.1 200 OK Content-Type: application/json
{ "response": { "data": [ { "url": "https://example.com/product/123", "shortCode": "spring-sale", "shortLinkName": "Spring sale — product 123", "smartLink": "https://re.codelloy.com/spring-sale", "isActive": true, "cloakEnabled": false, "enableCustomMetadata": false, "createdAt": "2026-05-15T10:30:00.742912Z", "updatedAt": "2026-05-15T10:30:00.742912Z", "createdBy": "api-key:Production Integration", "updatedBy": "api-key:Production Integration", "createdVia": "EXTERNAL", "lastModifiedVia": "EXTERNAL" } ], "page": { "size": 10, "number": 0, "totalElements": 42, "totalPages": 5 } } }

createdAt / updatedAt are ISO-8601 instants in UTC with a trailing Z, at microsecond precision where the value has it (e.g. "2026-05-15T10:30:00.742912Z"). Parse them with an instant or offset-aware parser: JavaScript Date, Python datetime.fromisoformat (3.11+), Java Instant.parse / OffsetDateTime.parse. Request bodies accept both this form and the earlier zone-less form ("2026-05-15T10:30:00").

Query DSL: Filtering Syntax

The query parameter accepts structured expressions in the form field=value. Multiple expressions can be combined with AND / OR.

FieldTypeExample
urlstringurl=https://example.com/page
shortCodestringshortCode=spring-sale
shortLinkNamestringshortLinkName=Spring sale
smartLinkstringsmartLink=https://re.codelloy.com/abc
isActivebooleanisActive=true
enableCustomMetadatabooleanenableCustomMetadata=true
cloakEnabledbooleancloakEnabled=true
createdBystringcreatedBy=alice@example.com or createdBy=api-key:Production Integration
updatedBystringSame shape as createdBy.
createdViaenumcreatedVia=EXTERNAL or createdVia=INTERNAL (case-insensitive). Filter for rows created via the External API or the dashboard.
lastModifiedViaenumSame shape as createdVia; matches the most recent write source.

Combine expressions with AND (intersection) or OR (union). Whitespace around operators is optional.

# All EXTERNAL-created links that are still active curl -X GET "https://api.codelloy.com/link/external/v1/shortenedUrl?query=createdVia=EXTERNAL AND isActive=true" \ -H "X-API-KEY: your_api_key_here"
# Audit which of your links are cloaked curl -X GET "https://api.codelloy.com/link/external/v1/shortenedUrl?query=cloakEnabled=true" \ -H "X-API-KEY: your_api_key_here"

Unknown enum values return HTTP 400 VE001 with a “Invalid value ‘X’ for enum ChangeSource” message; valid values are INTERNAL and EXTERNAL.

There is no separate GET-by-id endpoint — and you don’t need one. shortCode is the stable key of a link (the save endpoint upserts on it), and the query DSL matches it with exact equality, so filtering the list endpoint by shortCode returns exactly one row:

curl -X GET "https://api.codelloy.com/link/external/v1/shortenedUrl?query=shortCode=spring-sale" \ -H "X-API-KEY: your_api_key_here"

The response is the standard paginated envelope with data containing the single matching link and page.totalElements: 1. If no link with that short code exists in your organisation, you get 200 OK with an empty data: [] — not a 404.

Looking for a link’s click history instead of its configuration? Filter the analytics events endpoint by the same shortCode.

Last updated on