Appy

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.

ExportWhereWhat it contains
Links page CSVCampaigns > 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 CSVThe file you uploaded to Branch to create links in bulkcampaign,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 APIGET https://api2.branch.io/v1/url?url={link}&branch_key={key}, one link per callEvery 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.

links.txt to branch-links.csv
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.txt

Any 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 keyIn Appy
$desktop_urlWebsite: where computers and devices without a target go
$fallback_urlWebsite 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_urliPhone target. iPads use it too unless $ipad_url is set.
$ipad_urliPad target
$android_urlAndroid target
$huawei_urlHuawei AppGallery target
$samsung_urlGalaxy Store target
$deeplink_path, $ios_deeplink_path, $android_deeplink_pathDeep 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, ~campaignutm_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_titleLink title in the dashboard
$og_title, $og_description, $og_image_urlSocial 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 pathThe 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_video and 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 example acme://product/42.
  • Keys Branch generates, such as ~id, ~creation_source and +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. 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. 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. 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. 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.

Your branded domainBranch domainPrinted QR codego.acme.com/springgo.acme.comCNAME cname.appy.toImported Appy link/springacme.app.link/springshared earlierStays at Branchcannot point to AppyNew Appy linkshare or reprint
Branded domains move with one DNS change and keep their paths. Links on 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 linkWhat happens
On your branded domain, such as go.acme.com/springAdd 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.ltThese 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 takenThe 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 domainsThe 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-association and assetlinks.json on go.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.

bash
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://"}
  }'
response (shortened)
{
  "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": []
    }
  ]
}
  • status is ready (dry run only), imported, exists, error or over_limit.
  • aliasKept is true when the old path equals the new one, so the old URL works once its domain points to Appy.
  • issues carry a stable code, a severity and an English message; unsupported and ignored list 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.link and bnc.lt links 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.