Idempotent Link Creation
The problem
A common pattern in mobile apps is to call the link creation API every time a user taps "Share". Functionally this works — every tap produces a working link. But it creates a problem: thousands of duplicate links pointing at the same content, each receiving only a handful of clicks.
The result: a distorted "average clicks per link" metric, an inflated total link count, and noisy attribution data.
The fix: pass externalId
Every link-creation endpoint accepts an optional externalId field. ULink scopes externalId to your project and enforces uniqueness: if you call create with an externalId that already exists in your project, ULink returns the existing link instead of creating a duplicate.
Pick a deterministic key from your own system — typically a composite of the IDs that uniquely identify what the link points at:
| Use case | Suggested externalId |
|---|---|
| User profile sharing | profile:${userId} |
| Post sharing (per-user) | share:user:${userId}:post:${postId} |
| List sharing (per-user) | share:user:${userId}:list:${listId} |
| Campaign link | campaign:${campaignId} |
The key can be anything from 1 to 255 characters, no whitespace. ULink does not interpret its contents — it's just an identifier.
REST API example
# First call — creates the link, returns 201
curl -X POST https://api.ulink.ly/sdk/links \
-H "X-App-Key: $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "unified",
"iosUrl": "myapp://post/456",
"androidUrl": "myapp://post/456",
"fallbackUrl": "https://example.com/post/456",
"externalId": "share:user:123:post:456"
}'
# → 201 Created { "slug": "abc123", "shortUrl": "https://shared.ly/abc123", ... }
# Subsequent calls with the same externalId — returns the same link, 200
curl -X POST https://api.ulink.ly/sdk/links \
-H "X-App-Key: $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "unified",
"iosUrl": "myapp://post/456",
"androidUrl": "myapp://post/456",
"fallbackUrl": "https://example.com/post/456",
"externalId": "share:user:123:post:456"
}'
# → 200 OK { "slug": "abc123", "shortUrl": "https://shared.ly/abc123", ... }
Semantics
- Scope: per project. Two different projects can each have a link with
externalId = "x"— they won't collide. - Status code:
201 Createdwhen a new link is created,200 OKwhen an existing link is returned. - Strict idempotency: if a link already exists for an
externalId, subsequent calls ignore the payload (differentiosUrl,androidUrl,metadataare NOT applied to the existing link) and return the existing link as-is. To change a link's target URLs, usePUT /links/:id. - Immutability:
externalIdcannot be changed after a link is created. Attempts viaPUTreturn400 Bad Request. If you need a different identity, create a new link. - Backward compatible:
externalIdis optional. Existing integrations that omit it keep working unchanged — each call creates a new link, as before.
Validation
ULink rejects invalid externalId values with 400 Bad Request:
- Must be 1–255 characters
- Must not contain whitespace (space, tab, newline, etc.)
- Must be a string (numbers and other types are rejected)
When you don't need externalId
You can skip externalId for one-off links: marketing campaigns where each link is intentionally unique, A/B test variants, throwaway short URLs. The field is purely opt-in.
Migrating an existing integration
If your app already creates duplicate links today, adding externalId only prevents future duplicates. To clean up existing duplicates retroactively, contact support@ulink.ly — we can run a one-time dedup migration that merges duplicate links and consolidates click counts onto the canonical link.
Frontend best practices (still useful)
Even with externalId, you should:
- Debounce share buttons — prevents concurrent API calls before the first response lands.
- Cache locally — store the returned URL in your app state for the user's session so re-shares don't even need the network round-trip.
externalId is the safety net; local caching is the latency win.