Migrate
Migrate from Branch
Move your Branch links to Appy with a checked import. This guide covers which exports to use, what carries over, how printed QR codes on your own domain keep working and what stays with Branch.
Before you start
- The import is in the dashboard under Links > Import links and on the REST API as
POST /v1/links/import(Business and Enterprise). - Every plan can import within its link limit: Free 4, Pro 10, Business 500, Enterprise without a limit. Custom paths and deep links need Pro, custom domains need Business.
- One file holds up to 1000 links and 2 MB in the dashboard, or 480 KB per API request.
Nothing moves until you say so
The import first runs a check that writes nothing and shows every link: its new address, where each device goes and what is left behind. Links are created only when you confirm, and your Branch account keeps working throughout.
1. Export your links from Branch
Branch has no single file with every link and every setting. Use the export that matches how your links were made, or combine them in one CSV.
| Export | Where | What it contains |
|---|---|---|
| Links page CSV | Campaigns > Links, then the download icon. Up to 10,000 links per export. | Links created in the Branch dashboard. Branch does not document the columns, so check the Preview step to see where each column went. |
| Bulk Link Creation CSV | The file you uploaded to Branch to create links in bulk | campaign,channel,feature,stage,tags,alias,data, with the link settings as JSON in data. It has aliases but no full links, so enter your old link domain under Defaults. |
| Deep Linking API | GET https://api2.branch.io/v1/url?url={link}&branch_key={key}, one link per call | Every key of the link, including links created with the Branch API or SDK |
API and SDK links are missing from the dashboard export
Branch does not show links created with its API or SDK in the dashboard, and there is no endpoint that lists them. Start from your own list of those URLs, for example from your database or campaign sheets, and read each one with the Deep Linking API. Branch notes that reading a short link resets its expiration window.
This script reads every link in links.txt (one URL per line) and writes a CSV with a url column and the full API response in a data column, which the import reads as is.
export BRANCH_KEY="key_live_..."
printf 'url,data\n' > branch-links.csv
while IFS= read -r link; do
data=$(curl -s --get "https://api2.branch.io/v1/url" \
--data-urlencode "url=$link" \
--data-urlencode "branch_key=$BRANCH_KEY")
printf '%s,"%s"\n' "$link" "$(printf '%s' "$data" | sed 's/"/""/g')" >> branch-links.csv
done < links.txtAny CSV works as long as it has a header row. Columns named after Branch keys ($ios_url, ~campaign), a url or Short link column, an alias column, a data JSON column and the display names of Branch’s web-link bulk file (Link Title, Original Web URL, Link Alias) are all read. Comma, semicolon and tab separators are detected automatically.
2. What carries over
| Branch key | In Appy |
|---|---|
$desktop_url | Website: where computers and devices without a target go |
$fallback_url | Website when there is no $desktop_url. Like in Branch, it also covers phones that have no store URL of their own. When $desktop_url is set and differs, $fallback_url becomes the iPhone and Android destination. |
$ios_url | iPhone target. iPads use it too unless $ipad_url is set. |
$ipad_url | iPad target |
$android_url | Android target |
$huawei_url | Huawei AppGallery target |
$samsung_url | Galaxy Store target |
$deeplink_path, $ios_deeplink_path, $android_deeplink_path | Deep link: your app’s URI scheme plus the path, for example acme://sale/spring (Pro and above). Enter the scheme under Defaults. Appy keeps one deep link per link; when the iOS and Android paths differ, the iOS one is used and the other is listed. |
~channel, ~feature, ~campaign | utm_source, utm_medium and utm_campaign on the website URL, unless the URL already has them. This mapping is Appy’s choice; store URLs get no UTM tags. |
$marketing_title, then $og_title | Link title in the dashboard |
$og_title, $og_description, $og_image_url | Social preview: the card chat apps and social networks show when the link is shared (Pro and above). Without $og_title, the link title is used. The image has to be an https address. |
| Alias or link path | The link’s path, when it fits Appy’s rules (see step 4) |
Not carried over
The report lists every key that does not move, for every link, so nothing disappears without a note. The most common ones:
- Other preview tags, such as
$og_type,$og_videoand the$twitter_*tags. - Deepviews and behaviour keys:
$ios_passive_deepview,$android_passive_deepview,$desktop_deepview,$web_only,$after_click_url,$ios_redirect_timeout,$uri_redirect_mode,$always_deeplink. - Other platforms and variants:
$windows_phone_url,$blackberry_url,$fire_url,$ios_wechat_url,$android_wechat_url, and the country variants such as$ios_url_xx. Appy has country rules for Android instead. - Extra labels:
~tags,~stage,~campaign_id,~secondary_publisher,~ad_set_name,$3p. - Custom data keys that your app reads through the Branch SDK, such as
product_id. Put what the app needs into the deep link instead, for exampleacme://product/42. - Keys Branch generates, such as
~id,~creation_sourceand+url, are listed as ignored. - Click history stays in Branch. Appy counts clicks from the moment a link is imported.
3. Run the import
- 1
Upload
Open Links > Import links, choose Branch and add the CSV. Under Defaults, fill in what your links leave out: the website, the App Store and Google Play pages, your app’s URI scheme and, for files without full links, the old link domain. Branch sets the App Store and Google Play pages once in Link Settings, so exports often leave them out. Rows that set their own value keep it.
- 2
Preview
See which column went where, which keys are not carried over and which domains the links use, with what each domain needs to keep working.
- 3
Check
Every link is validated without writing anything: the path rules, every URL, the domain blocklist and your plan’s link limit. Each link shows its new address, where each device goes and a note for anything that changes. Download the report to share it or to plan reprints.
- 4
Import
The links that are ready are created in file order until your plan’s link limit is reached; the rest are reported as over the limit. Run the same file again after fixing a row: links already in your account are skipped.
4. Keep printed links working
app.link stay with Branch.The alias becomes the Appy path when it fits Appy’s rules: 5 to 64 characters, letters, numbers and dashes, not reserved and not already used by another Appy account. Appy paths are not case-sensitive, so /Spring-Sale and /spring-sale open the same link. Links without an alias, like acme.app.link/WgiqvsepqF, keep their generated ID as the path when it fits too.
| Old link | What happens |
|---|---|
On your branded domain, such as go.acme.com/spring | Add the domain under Custom domains (Business and Enterprise) and change its CNAME from custom.bnc.lt to cname.appy.to. Every imported link that kept its path opens again on the same URL, so printed QR codes keep working. |
On app.link, such as acme.app.link/spring, or on test-app.link or bnc.lt | These domains belong to Branch and cannot point to Appy. Share or reprint the new link before you close the Branch account. |
Alias shorter than 5 characters, with _ or /, reserved or taken | The link gets a new five-character path. The report lists the reason, and the downloadable report has the old and new link side by side for reprinting. |
| Two aliases that differ only in case, or the same alias on two domains | The first keeps the path and the other gets a new one, because every Appy path is unique and works on all your domains. |
Change the DNS record last
Import every link first and read the report. Once the CNAME points to Appy, Branch no longer serves that domain, so a path that was not imported opens your root redirect URL or a not-found page. If Branch serves your domain through its own nameservers, move the records to your DNS provider first; see Custom domains for root domains.
5. Move the SDK separately
Importing links does not touch your app. Links route by device and open the app through the deep link on every paid plan without any SDK. Universal Links and App Links on go.acme.com follow the domain: once its DNS points to Appy, iOS and Android read the association files from Appy, so set them up before the switch.
- On Enterprise, register the app under Apps & SDK and assign the domain to it, so Appy serves
apple-app-site-associationandassetlinks.jsonongo.acme.com(app links on your domain). - Replace the Branch SDK calls with the Appy SDK for iOS or Android, or use two HTTP calls from React Native, Flutter or Unity. Values your app read from Branch link data now come from the deep link URL.
- Ship the app update before or together with the DNS change, so new installs and returning users land in the app.
Import with the API
Send the file’s contents as csv. Run with "dryRun": true first: the response is the same report the dashboard shows and nothing is written. Send the same request with "dryRun": false to create the ready links. The API reference has the full schema.
curl https://api.appy.to/v1/links/import \
-H "Authorization: Bearer $APPY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "branch",
"dryRun": true,
"csv": "url,$fallback_url,$ios_url,$android_url,~campaign\nhttps://go.acme.com/spring,https://acme.com/spring,https://apps.apple.com/app/id123456789,https://play.google.com/store/apps/details?id=com.acme.shop,spring\n",
"defaults": {"deeplinkScheme": "acme://"}
}'{
"dryRun": true,
"source": "branch",
"plan": "business",
"limit": 500,
"used": 37,
"summary": {
"rows": 1,
"ready": 1,
"imported": 0,
"existing": 0,
"errors": 0,
"overLimit": 0,
"aliasesKept": 1,
"aliasesChanged": 0,
"withWarnings": 0
},
"domains": [
{
"host": "go.acme.com",
"kind": "branded",
"rows": 1,
"appyStatus": "not_added"
}
],
"rows": [
{
"line": 2,
"status": "ready",
"alias": "spring",
"slug": "spring",
"aliasKept": true,
"fallbackUrl": "https://acme.com/spring?utm_campaign=spring",
"targets": {
"iphone": "https://apps.apple.com/app/id123456789",
"android": "https://play.google.com/store/apps/details?id=com.acme.shop"
},
"unsupported": [],
"ignored": [],
"issues": []
}
]
}statusisready(dry run only),imported,exists,errororover_limit.aliasKeptistruewhen the old path equals the new one, so the old URL works once its domain points to Appy.issuescarry a stablecode, aseverityand an Englishmessage;unsupportedandignoredlist the keys of that row that did not move.- Rate limits and errors are the same as for the other link endpoints.
Limits
app.link,test-app.linkandbnc.ltlinks cannot move; only branded domains can.- One deep link per link; per-platform deep link paths that differ keep the iOS one.
- No Deepviews, Windows, BlackBerry, Fire OS or WeChat routes.
- Up to 1000 links per file. Larger exports go in several files; links already imported are skipped, so files can overlap.
- Imports count toward your plan’s link limit like any other link; rows that do not fit are reported, not dropped.
- Moving the SDK, analytics history and Branch integrations is a separate step.