Links
Manage smart links via the API. All endpoints require authentication.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /link/create | Create a link |
PUT | /link/update | Update a link |
DELETE | /link/delete | Delete a link |
POST | /links/bulk-create | Bulk create links |
GET | /links/{id}/analytics | Get link analytics |
GET | /links/analytics | Get all links analytics |
Rule Object
Rules define where a link redirects. Every link must have at least one rule with isDefault: true — this is the fallback destination when no other rule's conditions match.
Non-default rules are evaluated in order. The first rule whose conditions all match wins. If no rule matches, the default rule is used.
Rule IDs are generated automatically by the server — do not include id in your request.
Constraints:
- Exactly one rule per link must have
isDefault: true - The default rule is a pure fallback — it cannot have any conditions (
country,city,device,os,browser,timeStart,timeEnd,daysOfWeek). Conditions are only allowed on non-default rules timeStartandtimeEndmust be provided together or both omitted
| Field | Type | Required | Description |
|---|---|---|---|
targetUrl | string | ✅ | Destination URL. Any valid URL including deep links (e.g. youtube://, myapp://) |
isDefault | boolean | ✅ | Exactly one rule per link must have true |
country | string[] | — | ISO 3166-1 alpha-2 country codes (e.g. ["UA", "PL"]). Only visitors from these countries match |
city | string[] | — | City names (e.g. ["Kyiv", "Warsaw"]). Requires country to be set — cannot use city without country |
device | string[] | — | Device types. Accepted values: "desktop", "mobile", "tablet", "console", "smarttv", "wearable", "embedded" |
os | string[] | — | Operating systems. Accepted values: "Windows", "macOS", "Linux", "iOS", "Android" |
browser | string[] | — | Browsers. Accepted values: "Chrome", "Firefox", "Safari", "Edge", "Opera", "Brave" |
timeStart | string | — | Start of active time window in "HH:MM" format (e.g. "09:00") |
timeEnd | string | — | End of active time window in "HH:MM" format (e.g. "18:00") |
daysOfWeek | number[] | — | Days when the rule is active. 0 = Sunday … 6 = Saturday (e.g. [1,2,3,4,5] for weekdays) |
Full rule example (all optional condition fields):
{
"targetUrl": "https://example.com/ua-mobile",
"isDefault": false,
"country": ["UA", "PL"],
"city": ["Kyiv", "Warsaw"],
"device": ["mobile"],
"os": ["iOS", "Android"],
"browser": ["Chrome", "Safari"],
"timeStart": "09:00",
"timeEnd": "21:00",
"daysOfWeek": [1, 2, 3, 4, 5]
}
A/B Test Object
A/B testing splits traffic randomly between multiple destination URLs based on weights. When abTest.enabled is true, routing rules are ignored — each visitor is sent to one of the variants.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | ✅ | Set to true to activate A/B testing |
variants | Variant[] | ✅ | List of variants. Minimum 2 when enabled |
Variant fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✅ | Unique identifier for the variant. Use any stable string (e.g. a timestamp) — the server stores it as-is for tracking |
name | string | ✅ | Display name (e.g. "Variant A") |
url | string | ✅ | Destination URL for this variant |
weight | number | ✅ | Traffic share. All variant weights must sum to exactly 100 |
Example:
{
"abTest": {
"enabled": true,
"variants": [
{ "id": "1714000000000", "name": "Variant A", "url": "https://example.com/a", "weight": 60 },
{ "id": "1714000000001", "name": "Variant B", "url": "https://example.com/b", "weight": 40 }
]
}
}
To disable A/B testing when updating a link:
{
"abTest": { "enabled": false, "variants": [] }
}
QR Settings Object
Controls the visual appearance of the QR code generated for a link.
| Field | Type | Description |
|---|---|---|
color | string | Foreground color in hex format. Default: "#000000" |
bgColor | string | Background color in hex format. Default: "#ffffff" |
useLogo | boolean | Whether to embed a logo in the center of the QR code. Default: false |
logo | string | Logo image as a data URL (e.g. "data:image/png;base64,...") or a URL. Required when useLogo is true |
Example:
{
"qrSettings": {
"color": "#1a1a2e",
"bgColor": "#ffffff",
"useLogo": true,
"logo": "https://example.com/logo.png"
}
}
Create a link
POST /link/create
Request
POST https://api.revolink.link/apiv1/link/create
Content-Type: application/json
X-Api-Key: rvlnk_your_api_key_here
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
name | string | ✅ | Display name for the link |
rules | Rule[] | ✅ | Routing rules. At least one rule with isDefault: true required |
description | string | — | Internal description (not shown to visitors). Max 1000 characters |
slug | string | — | Custom URL slug. Auto-generated if omitted. Lowercase letters, numbers, hyphens only. Max 100 chars |
domain | string | — | Custom domain hostname (e.g. "go.mycompany.com"). Must be a verified domain in the workspace. Defaults to rvlnk.link |
active | boolean | — | Default: true |
clicksLimit | number | — | Positive integer. No limit if omitted |
expirationDate | string | — | ISO 8601 datetime. Must be in the future |
tagIds | string[] | — | Array of tag ObjectIds |
abTest | object | — | A/B test configuration. See A/B Test Object |
qrSettings | object | — | QR code appearance. See QR Settings Object |
Minimal example
curl -X POST https://api.revolink.link/apiv1/link/create \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"name": "My link",
"rules": [
{
"targetUrl": "https://example.com",
"isDefault": true
}
]
}'
Full example (all fields)
curl -X POST https://api.revolink.link/apiv1/link/create \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"name": "Summer campaign",
"description": "Main landing for summer 2025 promo",
"slug": "summer-2025",
"domain": "go.mycompany.com",
"active": true,
"clicksLimit": 10000,
"expirationDate": "2025-09-01T00:00:00Z",
"tagIds": ["664abc000000000000000010", "664abc000000000000000011"],
"rules": [
{
"targetUrl": "https://example.com/ua-mobile",
"isDefault": false,
"country": ["UA"],
"device": ["mobile"],
"os": ["iOS", "Android"],
"timeStart": "08:00",
"timeEnd": "22:00",
"daysOfWeek": [1, 2, 3, 4, 5]
},
{
"targetUrl": "https://example.com/de",
"isDefault": false,
"country": ["DE", "AT", "CH"],
"device": ["desktop"],
"browser": ["Chrome", "Firefox"]
},
{
"targetUrl": "https://example.com",
"isDefault": true
}
]
}'
Response 200 OK
{
"message": "Link \"Summer campaign\" successfully created.",
"id": "664abc123def456789000001",
"slug": "summer-2025",
"fullLink": "https://go.mycompany.com/summer-2025"
}
Error Responses
| Status | Body | Cause |
|---|---|---|
400 | {"error": "Name is required."} | name field is missing |
400 | {"error": "rules must be a non-empty array."} | rules is missing or empty |
400 | {"error": "Domain is not available for this workspace."} | Provided domain is not a verified domain of this workspace |
403 | {"licenseLimitReached": true, ...} | Plan link or rules quota reached |
409 | {"error": "This slug is already taken."} | Provided slug is already in use on this domain |
Update a link
PUT /link/update Only fields you provide are changed — all other fields remain unchanged.
Request
PUT https://api.revolink.link/apiv1/link/update
Content-Type: application/json
X-Api-Key: rvlnk_your_api_key_here
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
linkId | string | ✅ | ObjectId of the link to update |
name | string | — | New display name |
description | string | — | New description |
rules | Rule[] | — | Replaces all routing rules |
active | boolean | — | Enable or disable the link |
clicksLimit | number | — | New click limit. Pass null to remove the limit |
expirationDate | string | — | New expiration datetime. Pass null to remove expiration |
tagIds | string[] | — | Replaces the tag list |
Minimal example (disable a link)
curl -X PUT https://api.revolink.link/apiv1/link/update \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"linkId": "664abc123def456789000001",
"active": false
}'
Full example (update all fields)
curl -X PUT https://api.revolink.link/apiv1/link/update \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"linkId": "664abc123def456789000001",
"name": "Summer campaign (updated)",
"description": "Updated description",
"active": true,
"clicksLimit": 50000,
"expirationDate": "2025-12-31T23:59:59Z",
"tagIds": ["664abc000000000000000012"],
"rules": [
{
"targetUrl": "https://example.com/ua-mobile-v2",
"isDefault": false,
"country": ["UA", "PL"],
"device": ["mobile"]
},
{
"targetUrl": "https://example.com/v2",
"isDefault": true
}
]
}'
Remove limits example
curl -X PUT https://api.revolink.link/apiv1/link/update \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"linkId": "664abc123def456789000001",
"clicksLimit": null,
"expirationDate": null
}'
Response 200 OK
{
"message": "Link updated.",
"id": "664abc123def456789000001"
}
Error Responses
| Status | Body | Cause |
|---|---|---|
400 | {"error": "Invalid linkId format."} | linkId is not a valid ObjectId |
403 | {"error": "You do not have permission to edit this link."} | Link belongs to a different workspace |
403 | {"licenseLimitReached": true, ...} | Rules limit reached on current plan |
404 | {"error": "Link not found."} | No link with this ID exists |
Delete a link
DELETE /link/delete
Request
DELETE https://api.revolink.link/apiv1/link/delete
Content-Type: application/json
X-Api-Key: rvlnk_your_api_key_here
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
linkId | string | ✅ | ObjectId of the link to delete |
Example
curl -X DELETE https://api.revolink.link/apiv1/link/delete \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"linkId": "664abc123def456789000001"
}'
Response 200 OK
{
"message": "Link deleted."
}
Error Responses
| Status | Body | Cause |
|---|---|---|
400 | {"error": "Invalid linkId format."} | linkId is not a valid ObjectId |
403 | {"error": "You do not have permission to delete this link."} | Link belongs to a different workspace |
404 | {"error": "Link not found."} | No link with this ID exists |
Bulk create links
POST /links/bulk-create Up to 100 links per call. Each link is processed independently — if one fails, the rest still proceed.
Request
POST https://api.revolink.link/apiv1/links/bulk-create
Content-Type: application/json
X-Api-Key: rvlnk_your_api_key_here
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
links | LinkInput[] | ✅ | Array of link objects. Maximum 100 items |
Each item in links accepts the same fields as /link/create, except workspaceId (provided once at the top level).
Example
curl -X POST https://api.revolink.link/apiv1/links/bulk-create \
-H "Content-Type: application/json" \
-H "X-Api-Key: rvlnk_your_api_key_here" \
-d '{
"workspaceId": "664abc000000000000000001",
"links": [
{
"name": "Campaign — Ukraine mobile",
"slug": "ua-mob",
"description": "UA mobile audience",
"active": true,
"clicksLimit": 5000,
"expirationDate": "2025-12-31T23:59:59Z",
"tagIds": ["664abc000000000000000010"],
"rules": [
{
"targetUrl": "https://example.com/ua",
"isDefault": false,
"country": ["UA"],
"device": ["mobile"]
},
{
"targetUrl": "https://example.com",
"isDefault": true
}
]
},
{
"name": "Campaign — Germany",
"rules": [
{
"targetUrl": "https://example.com/de",
"isDefault": false,
"country": ["DE", "AT", "CH"]
},
{
"targetUrl": "https://example.com",
"isDefault": true
}
]
},
{
"name": "Simple redirect",
"rules": [
{
"targetUrl": "https://example.com/simple",
"isDefault": true
}
]
}
]
}'
Response 200 OK
The response always returns 200 even if some items failed. Check the failed array for per-item errors.
{
"message": "Bulk create complete: 3 created, 0 failed.",
"created": [
{
"id": "664abc123def456789000001",
"slug": "ua-mob",
"fullLink": "https://rvlnk.link/ua-mob",
"name": "Campaign — Ukraine mobile"
},
{
"id": "664abc123def456789000002",
"slug": "xk7r2p",
"fullLink": "https://rvlnk.link/xk7r2p",
"name": "Campaign — Germany"
},
{
"id": "664abc123def456789000003",
"slug": "m9qw1z",
"fullLink": "https://rvlnk.link/m9qw1z",
"name": "Simple redirect"
}
],
"failed": []
}
Partial failure example:
{
"message": "Bulk create complete: 2 created, 1 failed.",
"created": [
{
"id": "664abc123def456789000001",
"slug": "ua-mob",
"fullLink": "https://rvlnk.link/ua-mob",
"name": "Campaign — Ukraine mobile"
}
],
"failed": [
{
"input": {
"description": "Missing name and rules"
},
"error": "Each link must have name and rules."
}
]
}
Error Responses
| Status | Body | Cause |
|---|---|---|
400 | {"error": "links must be a non-empty array."} | links is missing or empty |
400 | {"error": "Maximum 100 links per request."} | More than 100 items provided |
403 | {"licenseLimitReached": true, ...} | Plan quota would be exceeded |
Get link analytics
GET /links/{id}/analytics
Returns click statistics for a single link. Available to all workspace members.
Request
GET https://api.revolink.link/apiv1/links/{id}/analytics?workspaceId=...&from=...&to=...
X-Api-Key: rvlnk_your_api_key_here
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
from | string | — | Start date in YYYY-MM-DD format. Defaults to the beginning of time |
to | string | — | End date in YYYY-MM-DD format. Defaults to today |
Example
curl "https://api.revolink.link/apiv1/links/664abc123def456789000001/analytics?workspaceId=664abc000000000000000001&from=2025-01-01&to=2025-04-30" \
-H "X-Api-Key: rvlnk_your_api_key_here"
Response 200 OK
{
"totals": {
"total": 1540,
"periodTotal": 320,
"unique": 210
},
"dailyTraffic": {
"2025-04-28": 45,
"2025-04-29": 61,
"2025-04-30": 38
},
"countries": {
"UA": { "title": "Ukraine", "value": 180, "flag": "UA" },
"DE": { "title": "Germany", "value": 54, "flag": "DE" }
},
"cities": {
"Kyiv": { "title": "Kyiv", "value": 92, "flag": "UA" }
},
"devices": {
"mobile": { "title": "Mobile", "value": 210 },
"desktop": { "title": "Desktop", "value": 110 }
},
"browsers": {
"Chrome": { "title": "Chrome", "value": 198 }
},
"os": {
"Android": { "title": "Android", "value": 140 }
},
"referrers": {
"instagram.com": { "title": "instagram.com", "value": 95 },
"Direct": { "title": "Direct", "value": 225 }
},
"rulesStats": {
"rule-id-1": {
"name": "Ukraine mobile",
"targetUrl": "https://example.com/ua",
"clicks": 140,
"uniqueClicks": 95,
"referrers": {
"Direct": { "title": "Direct", "value": 80 }
}
}
},
"abVariantsStats": {
"1714000000000": {
"name": "Variant A",
"targetUrl": "https://example.com/a",
"clicks": 185,
"uniqueClicks": 122,
"referrers": {}
}
}
}
Field descriptions:
| Field | Description |
|---|---|
totals.total | All-time total clicks for this link |
totals.periodTotal | Clicks within the requested date range |
totals.unique | Unique visitors within the date range (by IP + user agent) |
dailyTraffic | Clicks per day within the date range. Every day in the range is present, even if value is 0 |
countries / cities / devices / browsers / os | Breakdown by dimension. Each key maps to { title, value, flag? } |
referrers | Traffic sources. "Direct" means no referrer header |
rulesStats | Per-rule breakdown. Each key is the rule ID. Contains name, targetUrl, clicks, uniqueClicks, and referrers |
abVariantsStats | Per A/B variant breakdown. Each key is the variant ID. Contains name, targetUrl, clicks, uniqueClicks, and referrers. Empty object if A/B test is not enabled |
Error Responses
| Status | Body | Cause |
|---|---|---|
400 | {"error": "Invalid link id."} | id is not a valid ObjectId |
404 | {"error": "Link not found in this workspace."} | Link does not exist or belongs to a different workspace |
Get all links analytics
GET /links/analytics
Returns aggregated click statistics across all links in the workspace. Available to workspace owner and managers only.
Request
GET https://api.revolink.link/apiv1/links/analytics?workspaceId=...&from=...&to=...
X-Api-Key: rvlnk_your_api_key_here
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | string | ✅ | Workspace ObjectId |
from | string | — | Start date in YYYY-MM-DD format. Defaults to the beginning of time |
to | string | — | End date in YYYY-MM-DD format. Defaults to today |
Example
curl "https://api.revolink.link/apiv1/links/analytics?workspaceId=664abc000000000000000001&from=2025-04-01&to=2025-04-30" \
-H "X-Api-Key: rvlnk_your_api_key_here"
Response 200 OK
{
"totals": {
"total": 8420,
"periodTotal": 1750,
"unique": 1120
},
"dailyTraffic": {
"2025-04-01": 52,
"2025-04-02": 78
},
"links": {
"664abc123def456789000001": { "title": "Summer campaign", "value": 920 },
"664abc123def456789000002": { "title": "Product launch", "value": 830 }
},
"countries": {
"UA": { "title": "Ukraine", "value": 640, "flag": "UA" }
},
"cities": {
"Kyiv": { "title": "Kyiv", "value": 310, "flag": "UA" }
},
"devices": {
"mobile": { "title": "Mobile", "value": 980 },
"desktop": { "title": "Desktop", "value": 770 }
},
"browsers": {
"Chrome": { "title": "Chrome", "value": 1050 }
},
"os": {
"Android": { "title": "Android", "value": 620 },
"iOS": { "title": "iOS", "value": 360 }
}
}
Field descriptions:
| Field | Description |
|---|---|
totals.total | Total clicks across all workspace links within the date range |
totals.periodTotal | Same as total — clicks filtered to the requested date range |
totals.unique | Unique visitors in the date range |
links | Per-link breakdown. Each key is the link ObjectId. Value contains title (link name) and value (click count) |
dailyTraffic | Clicks per day across all links. Every day in the range is present |
countries / cities / devices / browsers / os | Aggregated breakdown across all links. Each key maps to { title, value, flag? } |
Error Responses
| Status | Body | Cause |
|---|---|---|
403 | {"error": "Only workspace owner or manager can access all links analytics."} | Authenticated user is a regular member, not owner or manager |