Endpoints
All endpoints share the base path:
https://api.codelloy.com/link/external/v1Authentication 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/saveRequest Headers
| Header | Value | Required |
|---|---|---|
X-API-KEY | Your API key | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Destination URL. Must be non-empty. |
shortCode | string | No | Custom 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. |
shortLinkName | string | No | Human-readable label shown in the dashboard list view. Strongly recommended for API-created links so operators can find them. |
customDomain | string | No | One 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. |
isActive | boolean | No | Defaults to true. Set to false to archive the link without deleting it (clients get HTTP 410 EN002 at click time). |
utmSource | string | No | UTM source tag (appended to the destination URL at click time). |
utmCampaign | string | No | UTM campaign tag. |
utmMedium | string | No | UTM medium tag. |
enableCustomMetadata | boolean | No | When 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. |
customMetadataTitle | string | No | Override title for link previews. |
customMetadataDescription | string | No | Override description for link previews. |
customMetadataImageUrl | string | No | Override image URL for link previews. |
deepLinks | array | No | Array 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.
| Field | Type | Direction | Description |
|---|---|---|---|
mobileAppId | string | Required on request | The 12-character ID from your Manage Apps dashboard. Identifies the registered MobileApp this entry targets. Null/blank/unknown values return DL001. |
deeplinkUrl | string | Request | Custom-scheme URI used when the app is installed (e.g. myapp://product/123). |
fallbackUrl | string | Request | Web URL used when fallbackEnabled=true and the app is not installed. |
linkBehaviour | string | Request | "app" (try the deep link first) or "webBrowser" (skip the app and go straight to url). |
fallbackEnabled | boolean | Request | When true, prefer fallbackUrl over the registered Store URL on app-not-installed. When false, prefer the Store URL. |
platform | string | Response only (server-controlled) | Reflects the registered MobileApp.platform ("IOS" / "ANDROID" / "WEB"). Values supplied in the request body are silently overwritten on save. |
storeUrl | string | Response only (server-controlled) | Reflects the registered MobileApp.storeUrl at read time. Edits via Manage Apps 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,
"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:00",
"updatedAt": "2026-05-15T10:30:00",
"createdBy": "api-key:Production Integration",
"updatedBy": "api-key:Production Integration",
"createdVia": "EXTERNAL",
"lastModifiedVia": "EXTERNAL"
}
}Custom domains:
smartLinkreflects 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:
createdByandupdatedBystart withapi-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.
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.
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/bulkSaveRequest Headers
| Header | Value | Required |
|---|---|---|
X-API-KEY | Your API key | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
data | array | Yes | Array 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
| Header | Value | Required |
|---|---|---|
X-API-KEY | Your API key | Yes |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 0 | Page number (zero-based). |
size | integer | 20 | Number of results per page. |
query | string | — | 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,
"enableCustomMetadata": false,
"createdAt": "2026-05-15T10:30:00.742912",
"updatedAt": "2026-05-15T10:30:00.742912",
"createdBy": "api-key:Production Integration",
"updatedBy": "api-key:Production Integration",
"createdVia": "EXTERNAL",
"lastModifiedVia": "EXTERNAL"
}
],
"page": {
"size": 10,
"number": 0,
"totalElements": 42,
"totalPages": 5
}
}
}
createdAt/updatedAtare serialised as JavaLocalDateTimewith no trailingZ(no timezone) and microsecond precision. Parse in your client with aLocalDateTime/naive datetimestrategy, not an instant-with-zone parser.
Query DSL: Filtering Syntax
The query parameter accepts structured expressions in the form field=value. Multiple expressions can be combined with AND / OR.
| Field | Type | Example |
|---|---|---|
url | string | url=https://example.com/page |
shortCode | string | shortCode=spring-sale |
shortLinkName | string | shortLinkName=Spring sale |
smartLink | string | smartLink=https://re.codelloy.com/abc |
isActive | boolean | isActive=true |
enableCustomMetadata | boolean | enableCustomMetadata=true |
createdBy | string | createdBy=alice@example.com or createdBy=api-key:Production Integration |
updatedBy | string | Same shape as createdBy. |
createdVia | enum | createdVia=EXTERNAL or createdVia=INTERNAL (case-insensitive). Filter for rows created via the External API or the dashboard. |
lastModifiedVia | enum | Same 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"Unknown enum values return HTTP 400 VE001 with a “Invalid value ‘X’ for enum ChangeSource” message; valid values are INTERNAL and EXTERNAL.