Index: src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
===================================================================
--- src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java	(revision 36328)
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java	(working copy)
@@ -43,7 +43,7 @@
         if (baseUrl != null && baseUrl.endsWith("/")) {
             baseUrl = baseUrl.substring(0, baseUrl.length()-1);
         }
-        this.id = info.getUrl();
+        this.id = info.getId();
         this.noTileHeaders = info.getNoTileHeaders();
         this.noTileChecksums = info.getNoTileChecksums();
         this.metadataHeaders = info.getMetadataHeaders();
Index: src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
===================================================================
--- src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 36329)
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(working copy)
@@ -80,7 +80,7 @@
      * Constructs a new {@code BingAerialTileSource}.
      */
     public BingAerialTileSource() {
-        super(new TileSourceInfo("Bing", null, null));
+        super(new TileSourceInfo("Bing", null, "Bing"));
         minZoom = 1;
     }
 
@@ -131,7 +131,7 @@
     protected URL getAttributionUrl() throws MalformedURLException {
         try {
             return new URI(FeatureAdapter.getSetting(METADATA_API_SETTING, METADATA_API_URL)
-                    .replace(API_KEY_PLACEHOLDER, FeatureAdapter.getSetting(API_KEY_SETTING, API_KEY))
+                    .replace(API_KEY_PLACEHOLDER, getKey())
                     .replace(API_KEY_LAYER, this.layer)).toURL();
         } catch (URISyntaxException e) {
             MalformedURLException malformedURLException = new MalformedURLException(e.getMessage());
@@ -140,6 +140,35 @@
         }
     }
 
+    /**
+     * Get the API key for Bing imagery
+     * Order of preference is as follows:
+     * <ol>
+     *     <li>Custom API key provided by {@link FeatureAdapter#getSetting(String, String)} via {@link #API_KEY_SETTING}</li>
+     *     <li>API key provided by {@link FeatureAdapter#retrieveApiKey(String)}</li>
+     *     <li>The hardcoded API key. This should not be used whenever possible.</li>
+     * </ol>
+     * @return The API key to use
+     */
+    private String getKey() {
+        // Preference order for key
+        // 1. Custom API key
+        // 2. Remote API key
+        // 3. Hardcoded API key
+        final String customKey = FeatureAdapter.getSetting(API_KEY_SETTING, API_KEY);
+        String key;
+        try {
+            key = FeatureAdapter.retrieveApiKey(this.getId());
+            if (!API_KEY.equals(customKey)) {
+                key = customKey;
+            }
+        } catch (IOException ioException) {
+            FeatureAdapter.getLogger(this.getClass()).log(Level.WARNING, "Failed to retrieve api key", ioException);
+            key = customKey;
+        }
+        return key;
+    }
+
     protected List<Attribution> parseAttributionText(InputSource xml) throws IOException {
         try {
             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
