Skip to content
noBSredir

Links

All link endpoints are scoped to a workspace: /api/workspaces/:wsId/links.

POST /workspaces/:wsId/links

Create a short link. Auto-generates a slug if not provided.

Role: editor+

Terminal window
curl -X POST https://nobsredir.com/api/workspaces/ws_abc123/links \
-H "X-API-Key: nobs_your_key" \
-H "Content-Type: application/json" \
-d '{
"target": "https://example.com/my-long-page",
"slug": "demo",
"domain": "go.yourco.com",
"title": "Demo page",
"utm_params": {"source": "newsletter", "medium": "email"},
"expires_at": "2025-12-31T23:59:59Z",
"fallback_url": "https://example.com/expired",
"routing_rules": {
"geo": {"DE": "https://example.de/seite", "FR": "https://example.fr/page"},
"device": {"mobile": "https://m.example.com/page"},
"os": {"iOS": "https://apps.apple.com/app/example", "Android": "https://play.google.com/store/apps/details?id=com.example"}
},
"rules": [
{
"name": "Germany",
"conditions": [{"field": "country", "operator": "eq", "value": "DE"}],
"action": {"type": "redirect", "url": "https://example.de/seite"}
}
],
"og_title": "Check this out",
"og_description": "A great page worth visiting",
"og_image": "https://example.com/og.png",
"tags": ["marketing", "q1-launch"],
"note": "Internal: approved by legal on Jan 15",
"ab_variants": [
{"name": "Control", "url": "https://example.com/page-v1", "weight": 60},
{"name": "Variant A", "url": "https://example.com/page-v2", "weight": 40}
],
"password": "launch2025",
"deep_link": {
"ios_app_url": "myapp://promo/123",
"ios_store_url": "https://apps.apple.com/app/example/id123",
"android_app_url": "myapp://promo/123",
"android_store_url": "https://play.google.com/store/apps/details?id=com.example",
"interstitial": true
}
}'

Body:

FieldTypeRequiredDescription
targetstringyesDestination URL. Must be a valid URL.
slugstringnoCustom slug. Letters, numbers, hyphens, underscores only. Auto-generated if omitted. Requires a custom domain - the default domain (fnl.sh) always uses auto-generated slugs.
domainstringnoDomain for the short link. Defaults to fnl.sh.
titlestringnoDisplay name in the dashboard.
tagsstring[]noTags for organizing links. Lowercase letters, numbers, hyphens, and underscores ([a-z0-9_-]), max 32 chars each, max 10 per link.
notestringnoInternal note. Visible only to workspace members - never leaked to visitors or in redirects.
utm_paramsobjectnoUTM parameters: {source?, medium?, campaign?, term?, content?}. Appended to target URL on redirect.
expires_atstringnoISO 8601 expiration date. After this, returns 410 or redirects to fallback_url.
fallback_urlstringnoRedirect target after expiration. Must be a valid URL. If not set, the workspace-level expired_page_url is used as a fallback (configure in workspace settings via PATCH /workspaces/:wsId).
routing_rulesobjectnoGeo/device/OS routing. Requires Team or Agency plan. {geo?: {country_code: url}, device?: {mobile|desktop|tablet: url}, os?: {iOS|Android|Windows|macOS|Linux: url}}.
ab_variantsarraynoA/B split testing. Requires Team or Agency plan. Array of 2-4 variants: [{name: string, url: string, weight: integer}]. Weights must be 1-99 and sum to 100. Variant names must be unique. If set, routing rules take priority - A/B applies only when no routing rule matches.
og_titlestringnoOpen Graph title for social previews.
og_descriptionstringnoOpen Graph description for social previews.
og_imagestringnoOpen Graph image URL for social previews. Must be a valid URL.
passwordstringnoPassword-protect the link. Must be at least 8 characters. Visitors see a branded password form before being redirected. The password is never stored or returned in API responses.
rulesarraynoConditional routing rules (Pro+). Ordered IF/THEN rules evaluated at redirect time - first match wins. See Rules engine for full field/operator reference.
template_idstringnoApply a link template’s config as defaults. The template must belong to the same workspace. Any field set on the link overrides the template. Requires Pro plan or higher. See Templates API.
interstitialobjectnoShow a custom page before redirecting. Requires Pro plan or higher. See Interstitials below.
sequenceobjectnoRedirect to a different URL on each visit. Requires Pro plan or higher. See Sequences below.
redirect_typeintegernoHTTP redirect status: 301 (permanent) or 302 (temporary, default). Use 301 to pass SEO link equity to the destination. Use 302 for campaigns, temporary links, or when the destination may change.
deep_linkobjectnoMobile deep linking configuration. Requires Pro plan or higher. See Deep Links below.

Response 201:

{
"id": "lnk_xyz789",
"domain": "go.yourco.com",
"slug": "demo",
"target": "https://example.com/my-long-page",
"title": "Demo page",
"utm_params": {"source": "newsletter", "medium": "email"},
"expires_at": "2025-12-31T23:59:59Z",
"fallback_url": "https://example.com/expired",
"routing_rules": {"geo": {"DE": "https://example.de/seite"}, "device": {"mobile": "https://m.example.com/page"}, "os": {"iOS": "https://apps.apple.com/app/example"}},
"rules": [{"name": "Germany", "conditions": [{"field": "country", "operator": "eq", "value": "DE"}], "action": {"type": "redirect", "url": "https://example.de/seite"}}],
"og_title": "Check this out",
"og_description": "A great page worth visiting",
"og_image": "https://example.com/og.png",
"tags": ["marketing", "q1-launch"],
"note": "Internal: approved by legal on Jan 15",
"ab_variants": [
{"name": "Control", "url": "https://example.com/page-v1", "weight": 60},
{"name": "Variant A", "url": "https://example.com/page-v2", "weight": 40}
],
"has_password": true,
"redirect_type": 302,
"deep_link": {
"ios_app_url": "myapp://promo/123",
"ios_store_url": "https://apps.apple.com/app/example/id123",
"android_app_url": "myapp://promo/123",
"android_store_url": "https://play.google.com/store/apps/details?id=com.example",
"interstitial": true
},
"short_url": "https://go.yourco.com/demo"
}

Errors:

  • 400 - Invalid target URL, slug format, expires_at, fallback_url, or og_image
  • 400 - Custom slug provided without a custom domain (default domain uses auto-generated slugs)
  • 400 - More than 10 tags
  • 400 - password less than 8 characters
  • 400 - redirect_type is not 301 or 302
  • 400 - ab_variants invalid (fewer than 2, more than 4, weights don’t sum to 100, duplicate names, invalid URLs, weights not 1-99)
  • 402 - Routing rules or A/B variants require a Team or Agency plan
  • 402 - Rules engine requires a Pro plan or higher
  • 402 - Interstitials require a Pro plan or higher
  • 402 - Sequences require a Pro plan or higher
  • 402 - Deep links require a Pro plan or higher
  • 400 - Invalid rules (bad field, operator, field/operator mismatch, invalid action URL, too many rules for plan)
  • 409 - Custom slug already exists on this domain
  • 429 - Rate limit exceeded (max 30 requests per 10 seconds per workspace)
  • 503 - Temporary error, try again

List links with pagination and filtering.

Role: viewer+

Terminal window
curl "https://nobsredir.com/api/workspaces/ws_abc123/links?page=1&limit=20&domain=fnl.sh&tag=marketing" \
-H "X-API-Key: nobs_your_key"

Query parameters:

ParamTypeDefaultDescription
pageint1Page number
limitint20Items per page
domainstring-Filter by domain
prefixstring-Filter by slug prefix (defaults to fnl.sh domain)
tagstring-Filter by tag. Only links with this tag are returned.

Response 200:

{
"links": [
{
"id": "lnk_xyz789",
"workspace_id": "ws_abc123",
"domain": "fnl.sh",
"slug": "a1b2c3",
"target": "https://example.com/page",
"title": null,
"tags": ["marketing"],
"note": null,
"utm_params": null,
"expires_at": null,
"fallback_url": null,
"routing_rules": null,
"rules": null,
"ab_variants": null,
"og_title": null,
"og_description": null,
"og_image": null,
"has_password": false,
"created_by": "usr_abc123",
"created_at": "2025-01-20T14:30:00.000Z",
"updated_at": "2025-01-20T14:30:00.000Z",
"click_count": 142
}
],
"total": 1,
"page": 1,
"limit": 20
}

GET /workspaces/:wsId/links/tags

List all tags used in the workspace, with metadata.

Role: viewer+

Terminal window
curl https://nobsredir.com/api/workspaces/ws_abc123/links/tags \
-H "X-API-Key: nobs_your_key"

Response 200:

{
"tags": [
{
"name": "marketing",
"color": "#ff0000",
"description": "Marketing campaign links",
"date_from": "2026-01-01",
"date_to": "2026-03-31"
},
{
"name": "product",
"color": "",
"description": "",
"date_from": null,
"date_to": null
}
]
}

Tags are returned sorted alphabetically. Only tags currently in use (assigned to at least one link) are included.


PUT /workspaces/:wsId/links/tags/:tagName

Create or update tag metadata. The tag name must be lowercase letters, numbers, hyphens, and underscores ([a-z0-9_-]).

Role: editor+

Terminal window
curl -X PUT https://nobsredir.com/api/workspaces/ws_abc123/links/tags/black-friday \
-H "X-API-Key: nobs_your_key" \
-H "Content-Type: application/json" \
-d '{
"color": "#ff0000",
"description": "Black Friday 2026 campaign",
"date_from": "2026-11-25",
"date_to": "2026-11-30"
}'

Body:

FieldTypeDescription
colorstringHex color (e.g. #ff0000) or empty string to clear
descriptionstringDescription, max 500 characters
date_fromstringCampaign start date (YYYY-MM-DD) or null
date_tostringCampaign end date (YYYY-MM-DD) or null

All fields are optional. Only provided fields are updated. Each workspace can have up to 100 tags.

Response 200:

{"ok": true}

Errors: 402 - Tag limit reached (max 100 per workspace).

Errors: 400 - Invalid color format, date format, or description too long. 403 - Requires editor+ role.


DELETE /workspaces/:wsId/links/tags/:tagName

Delete a tag. Removes the tag metadata and unlinks it from all links in the workspace.

Role: editor+

Terminal window
curl -X DELETE https://nobsredir.com/api/workspaces/ws_abc123/links/tags/old-campaign \
-H "X-API-Key: nobs_your_key"

Response 200:

{"ok": true}

GET /workspaces/:wsId/links/:linkId

Get a single link by ID.

Role: viewer+

Terminal window
curl https://nobsredir.com/api/workspaces/ws_abc123/links/lnk_xyz789 \
-H "X-API-Key: nobs_your_key"

Response 200: Same shape as a single item in the list response, including click_count, tags, note, has_password, ab_variants, rules, interstitial, and sequence.

Errors: 404 - Link not found.


PATCH /workspaces/:wsId/links/:linkId

Update a link. Only send the fields you want to change. Set a field to null to clear it.

Role: editor+

Terminal window
curl -X PATCH https://nobsredir.com/api/workspaces/ws_abc123/links/lnk_xyz789 \
-H "X-API-Key: nobs_your_key" \
-H "Content-Type: application/json" \
-d '{"tags": ["marketing", "updated"], "note": "Reviewed by team"}'

Body: Same fields as create, all optional. Send null to clear optional fields like tags, note, utm_params, expires_at, routing_rules, ab_variants, rules, password, interstitial, sequence, redirect_type, etc.

You can update any combination of fields in a single call - change the target, add tags, update the note, all at once. Changes take effect immediately.

Set password to a new string to change the password, or null to remove it. Set ab_variants to null to remove A/B testing. Set redirect_type to 301 or 302, or null to reset to the default (302).

Response 200:

{"ok": true}

Errors:

  • 400 - Invalid field values, no updates provided
  • 400 - Cannot set custom slug on default domain (fnl.sh)
  • 400 - More than 10 tags
  • 404 - Link not found
  • 409 - Slug already exists on this domain

DELETE /workspaces/:wsId/links/:linkId

Delete a link. Returns 404 for future redirects.

Role: admin+

Terminal window
curl -X DELETE https://nobsredir.com/api/workspaces/ws_abc123/links/lnk_xyz789 \
-H "X-API-Key: nobs_your_key"

Response 200:

{"ok": true}

Errors: 404 - Link not found.


POST /workspaces/:wsId/links/bulk

Bulk create up to 100 links. Links with invalid target URLs, duplicate custom slugs, or custom slugs on the default domain (fnl.sh) are silently skipped.

Role: editor+

Terminal window
curl -X POST https://nobsredir.com/api/workspaces/ws_abc123/links/bulk \
-H "X-API-Key: nobs_your_key" \
-H "Content-Type: application/json" \
-d '{
"links": [
{"target": "https://example.com/page-1", "slug": "p1"},
{"target": "https://example.com/page-2", "slug": "p2"},
{"target": "https://example.com/page-3"}
]
}'

Body:

FieldTypeRequiredDescription
linksarrayyesArray of link objects (same fields as single create). Max 100.

Response 201:

{
"created": [
{"id": "lnk_a1", "domain": "fnl.sh", "slug": "p1", "target": "https://example.com/page-1", "title": null},
{"id": "lnk_a2", "domain": "fnl.sh", "slug": "p2", "target": "https://example.com/page-2", "title": null},
{"id": "lnk_a3", "domain": "fnl.sh", "slug": "x7k9m2", "target": "https://example.com/page-3", "title": null}
],
"count": 3
}

Errors:

  • 400 - links array empty or exceeds 100 items
  • 429 - Rate limit exceeded (max 5 bulk requests per 10 seconds per workspace)

DELETE /workspaces/:wsId/links/bulk

Bulk delete up to 100 links by ID.

Role: admin+

Terminal window
curl -X DELETE https://nobsredir.com/api/workspaces/ws_abc123/links/bulk \
-H "X-API-Key: nobs_your_key" \
-H "Content-Type: application/json" \
-d '{"ids": ["lnk_a1", "lnk_a2", "lnk_a3"]}'

Body:

FieldTypeRequiredDescription
idsstring[]yesArray of link IDs. Max 100.

Response 200:

{"deleted": 3}

Non-existent IDs are silently ignored. deleted reflects how many were actually removed.


Rules Engine (Pro+)

Set up conditional routing with IF/THEN rules. Each rule has a name, an array of conditions (AND logic), and an action. Rules are evaluated top-to-bottom at redirect time - first match wins. If no rule matches, the link’s default target is used.

Set the rules array on create or update. Set to null on PATCH to clear all rules.

Rule object:

FieldTypeDescription
namestringHuman-readable label (e.g. “German visitors”)
conditionsarrayConditions that must ALL be true. Empty array = catch-all.
actionobject{"type": "redirect", "url": "https://..."}

Condition object:

FieldTypeDescription
fieldstringWhat to check. See available fields below.
operatorstringHow to compare: eq, neq, contains, starts_with, in, not_in, gt, lt, between
valuestring or string[]Value(s) to match. Use array for in, not_in, between.

Available fields by plan:

  • Pro: country, continent, device, os, browser
  • Team/Agency: all Pro fields plus language, referrer, time, day, query, cookie, click_count

Max rules per plan: Pro = 5, Team = 20, Agency = 50.

Example:

{
"rules": [
{
"name": "Germany",
"conditions": [{"field": "country", "operator": "eq", "value": "DE"}],
"action": {"type": "redirect", "url": "https://example.de/seite"}
},
{
"name": "Mobile users",
"conditions": [{"field": "device", "operator": "eq", "value": "mobile"}],
"action": {"type": "redirect", "url": "https://m.example.com"}
}
]
}

See the full Rules engine guide for all operators, field/operator compatibility, and more examples.

Priority: Rules engine > legacy geo/device/OS routing > A/B testing > default target.

Performance: Zero latency added. All condition data comes from request headers and connection metadata already available at redirect time.


Interstitials (Pro+)

Show a custom page between the click and the redirect. Set the interstitial object on create or update. Set to null on PATCH to remove.

FieldTypeDescription
titlestringPage heading (required, max 200 chars)
bodystringBody text (max 500 chars)
cta_textstringButton text (max 50 chars, default: “Continue”)
delayintegerAuto-redirect delay in seconds (1-30, default: 5)
show_oncebooleanShow once per visitor via cookie (default: true)
styleobjectCustom colors: bg_color, text_color, button_color (hex values)

Example:

{
"interstitial": {
"title": "Quick heads up",
"body": "You are leaving our site. The destination is managed by a third party.",
"cta_text": "Got it",
"delay": 5,
"show_once": true,
"style": {"bg_color": "#0a0a0a", "text_color": "#e5e5e5", "button_color": "#3b82f6"}
}
}

The interstitial page auto-redirects after the delay. Visitors can also click the CTA button to continue immediately. Social bots skip the interstitial entirely.

See the Interstitials guide for how interstitials interact with other features.


Sequences (Pro+)

Make a single link redirect to a different URL on each visit per visitor. Set the sequence object on create or update. Set to null on PATCH to remove.

FieldTypeDescription
urlsarrayOrdered list of 2-50 destinations. Each: {url: string, title?: string}
loopbooleanRestart from beginning after last URL (default: false - stays on last)

Example:

{
"sequence": {
"urls": [
{"url": "https://example.com/step-1", "title": "Welcome"},
{"url": "https://example.com/step-2", "title": "Setup"},
{"url": "https://example.com/step-3", "title": "Done"}
],
"loop": false
}
}

Step tracking uses a cookie per visitor per link. First visit serves URL 1, second visit URL 2, and so on.

With loop: false, the visitor stays on the last URL permanently. With loop: true, after the last URL they start back at URL 1.

Sequences resolve the target before routing rules, A/B testing, and other redirect logic. This means rules can still override the sequence-determined target.

See the Sequences guide for more details.


Configure mobile deep linking to open native apps when visitors tap your short link on iOS or Android. Set the deep_link object on create or update.

FieldTypeDescription
ios_app_urlstringiOS URI scheme or Universal Link URL (e.g. myapp://promo/123)
ios_store_urlstringApple App Store URL (fallback if app not installed)
ios_fallback_urlstringWeb fallback URL for iOS
android_app_urlstringAndroid URI scheme or App Link URL
android_store_urlstringGoogle Play Store URL (fallback if app not installed)
android_fallback_urlstringWeb fallback URL for Android
interstitialbooleanShow an app detection page that tries to open the app, then falls back to the store or web URL automatically

At least one of ios_app_url or android_app_url is required. Store and fallback URLs must be valid HTTP(S) URLs. App URLs can use custom URI schemes.

How it works:

  • When a mobile visitor taps the link, the redirect engine detects their platform (iOS/Android) and applies the deep link config.
  • With interstitial: true, a lightweight page attempts to open the app via the URI scheme. If the app isn’t installed (detected by timeout), it redirects to the store URL or web fallback.
  • Without interstitial, the visitor is redirected directly to the app URL (302).
  • Desktop visitors and social media bots are not affected - they get the normal redirect.
  • Deep links run after routing rules. If a geo/device/OS routing rule resolves a different target, that resolved target becomes the web fallback for the deep link.
  • Set deep_link to null in a PATCH request to remove deep linking from a link.

GET /workspaces/:wsId/links/:linkId/qr

Generate a QR code for a link. Returns the image directly (not JSON). Available on all plans.

Role: viewer+

Terminal window
curl https://nobsredir.com/api/workspaces/ws_abc123/links/LINK_ID/qr \
-H "X-API-Key: nobs_your_key" \
-o qr-code.svg

Query parameters:

ParamTypeDefaultDescription
formatstringsvgOutput format: svg or png
sizeinteger400Image dimensions in pixels (100-2000)
fgstring000000Foreground color as 6-character hex (no # prefix)
bgstringffffffBackground color as 6-character hex, or transparent (SVG only)
margininteger2Quiet zone around the QR code in modules (0-8)
ecstringMError correction level: L, M, Q, or H
downloadbooleanfalseSet to 1 or true for a download attachment response

Response: Raw image bytes with appropriate Content-Type header (image/svg+xml or image/png).

Examples:

Terminal window
# SVG with custom brand colors
curl "https://nobsredir.com/api/workspaces/ws_abc123/links/LINK_ID/qr?fg=1a1a2e&bg=e2e2e2" \
-H "X-API-Key: nobs_your_key" -o branded-qr.svg
# High-res PNG for print
curl "https://nobsredir.com/api/workspaces/ws_abc123/links/LINK_ID/qr?format=png&size=2000&ec=H" \
-H "X-API-Key: nobs_your_key" -o print-qr.png
# Transparent background SVG
curl "https://nobsredir.com/api/workspaces/ws_abc123/links/LINK_ID/qr?bg=transparent" \
-H "X-API-Key: nobs_your_key" -o transparent-qr.svg

Rate limit: 30 requests per minute per user.

Errors:

  • 400 - Invalid parameters (size out of range, bad color format, transparent with PNG)
  • 404 - Link not found or wrong workspace