Automate Firebase Dynamic Links migration
Some teams created Firebase links via backend jobs. This guide shows how to recreate those slugs with the ULink REST API.
1. Generate an API key
Inside the dashboard open Project → API Keys → Generate Key. The key maps to a single project ID and is sent via the X-App-Key header on every /sdk/* request.
2. Recreate links with POST /sdk/links
Creates a new dynamic or unified link.
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-App-Key | Yes | Your project API key |
Body
| Field | Type | Required | Description |
|---|---|---|---|
type | "dynamic" | "unified" | Yes | Link type |
slug | string | No | Custom slug (auto-generated if omitted) |
name | string | No | Human-readable name for the link (shown in the dashboard) |
domain | string | No | Verified domain host. Required when the project has more than one verified domain. |
iosFallbackUrl | string | No | iOS App Store fallback (dynamic links) |
androidFallbackUrl | string | No | Google Play fallback (dynamic links) |
iosUrl | string | No | iOS destination URL (unified links) |
androidUrl | string | No | Android destination URL (unified links) |
fallbackUrl | string | No | General web fallback URL |
parameters | object | No | Arbitrary key-value pairs passed to the app on open |
metadata | object | No | Custom metadata including social media tags (see below) |
Social media tags — The metadata object supports Open Graph fields that control how your link appears when shared on social platforms:
| Key | Description | Recommended |
|---|---|---|
ogTitle | Title shown in social previews | Up to 60 characters |
ogDescription | Description shown in social previews | Up to 155 characters |
ogImage | Image URL shown in social previews | 1200x630px, HTTPS URL |
ULink automatically renders these as <meta property="og:*"> and Twitter Card tags when social media crawlers fetch the link.
Example — dynamic link
curl -X POST "https://api.ulink.ly/sdk/links" \
-H "Content-Type: application/json" \
-H "X-App-Key: YOUR_ULINK_API_KEY" \
-d '{
"type": "dynamic",
"slug": "summer-offer",
"iosFallbackUrl": "https://apps.apple.com/app/id000000000",
"androidFallbackUrl": "https://play.google.com/store/apps/details?id=com.example.app",
"fallbackUrl": "https://example.com/campaign",
"parameters": {
"utm_source": "email",
"utm_campaign": "sunshine"
},
"metadata": {
"owner": "growth-team",
"ogTitle": "Summer Offer — 30% Off",
"ogDescription": "Grab our limited-time summer deal before it expires!",
"ogImage": "https://example.com/images/summer-offer.jpg"
},
"domain": "links.shared.ly"
}'
Notes
domainmust be one of the verified hosts returned byGET /projects/:id/domains. If multiple domains exist and you omit it, the API returns an error.- Use
type: "unified"withiosUrl/androidUrlinstead of fallback URLs. - If no
ogTitleis provided, ULink falls back to the link'snamefor social previews.
3. Retrieve a link — GET /sdk/links/:slug
Returns the full link object for a given slug. This endpoint is public and does not require authentication.
curl "https://api.ulink.ly/sdk/links/summer-offer"
4. Attach installations & sessions
ULink tracks MAU via /sdk/bootstrap, /sdk/installations/track, and /sdk/sessions/*. This telemetry is required so we can count monthly active users and enforce free-tier usage limits. If you previously forwarded Firebase analytics events, move them to these endpoints.
POST /sdk/bootstrap
Combines installation registration and session creation in a single call.
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-App-Key | Yes | Your project API key |
Body
| Field | Type | Required | Description |
|---|---|---|---|
installationId | string | No | Unique device identifier (generated server-side if omitted) |
persistentDeviceId | string | No | Stable ID for reinstall detection |
deviceModel | string | No | e.g. "iPhone16,2" |
deviceManufacturer | string | No | e.g. "Apple" |
osName | string | No | e.g. "iOS" |
osVersion | string | No | e.g. "18.0" |
appVersion | string | No | e.g. "3.4.1" |
appBuild | string | No | Build number |
language | string | No | e.g. "en-US" |
timezone | string | No | e.g. "America/New_York" |
metadata | object | No | Custom metadata |
Example
curl -X POST "https://api.ulink.ly/sdk/bootstrap" \
-H "Content-Type: application/json" \
-H "X-App-Key: YOUR_ULINK_API_KEY" \
-d '{
"installationId": "device-123",
"deviceModel": "iPhone16,2",
"osName": "iOS",
"osVersion": "18.0",
"appVersion": "3.4.1",
"metadata": {
"source": "ios-app"
}
}'
Response
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the call succeeded |
installationId | string | Persisted device identifier |
installationToken | string | Signed JWT you can attach to future resolve requests |
sessionId | string | null | Current session ID (null on free tier) |
sessionCreated | boolean | Whether a new session was created |
isReinstall | boolean | true if a previous installation was detected for this device |
POST /sdk/installations/track
Registers or updates an installation without starting a session.
Headers: same as bootstrap.
Body
| Field | Type | Required | Description |
|---|---|---|---|
installationId | string | Yes | Unique device identifier |
deviceModel | string | No | Device model |
deviceManufacturer | string | No | Manufacturer |
osName | string | No | OS name |
osVersion | string | No | OS version |
appVersion | string | No | App version |
appBuild | string | No | Build number |
language | string | No | Locale |
timezone | string | No | Timezone |
metadata | object | No | Custom metadata |
Response
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the call succeeded |
installationId | string | Persisted device identifier |
isNew | boolean | Whether this is a first-time installation |
installationToken | string | Signed JWT |
POST /sdk/sessions/start
Starts a new session for an existing installation.
Body
| Field | Type | Required | Description |
|---|---|---|---|
installationId | string | Yes | Installation to associate the session with |
networkType | string | No | e.g. "wifi", "cellular" |
deviceOrientation | string | No | e.g. "portrait" |
metadata | object | No | Custom metadata |
Response
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the call succeeded |
sessionId | string | UUID of the created session |
POST /sdk/sessions/:sessionId/end
Ends an active session. The SDKs call this automatically, but backend automation can also use it.
curl -X POST "https://api.ulink.ly/sdk/sessions/{sessionId}/end" \
-H "X-App-Key: YOUR_ULINK_API_KEY"
Response
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the session was ended |
5. Resolve links — GET /sdk/resolve
Resolves a full URL or deep link and tracks the click event. Use this to validate any migrated slug. The endpoint enforces domain/project matching plus usage limits.
Query parameters
| Param | Required | Description |
|---|---|---|
url | Yes | Full URL to resolve, e.g. https://links.shared.ly/example |
Optional headers
| Header | Description |
|---|---|
x-installation-token | JWT from a previous bootstrap/track call |
x-installation-id | Installation ID to attach |
x-ulink-client | Client identifier, e.g. "migration-script" |
Example
curl "https://api.ulink.ly/sdk/resolve?url=https://links.shared.ly/example" \
-H "User-Agent: cli-test" \
-H "X-ULink-Client: migration-script" \
-i
Inspect the JSON payload to ensure per-platform destinations and metadata match your expectations before flipping DNS or campaign URLs.
6. Deferred deep link matching — POST /sdk/deferred/match
Matches a deferred deep link by correlating a click fingerprint with an installation. This is used for attribution when a user clicks a link before the app is installed. Available on paid plans only.
Headers
| Header | Required | Description |
|---|---|---|
X-App-Key | Yes | Your project API key |
Body
| Field | Type | Required | Description |
|---|---|---|---|
fingerprint | object | Yes | Device fingerprint data |
clickId | string | No | Click ID to match against |
installationId | string | No | Installation ID |
7. Suggested migration script
- Export Firebase link inventory to CSV/JSON.
- Map each record to the
POST /sdk/linkspayload (choosedynamicvsunified, set verifieddomain, copy metadata/UTMs). - Call
/sdk/linksfor each entry; retry on rate-limit errors with exponential backoff. - Optionally bootstrap installations by replaying device data via
/sdk/installations/trackor/sdk/bootstrapif you stored it historically. - Use
/sdk/resolveto smoke-test a sample of the imported slugs.
With these APIs wired up, all automation previously built on Firebase Dynamic Links will point entirely at ULink's backend.