Unable accessing my own 3D Tiles asset on Cesium ion

I am experiencing a persistent problem accessing my own 3D Tiles asset on Cesium ion. Despite following all documentation and community advice, I am unable to access my asset with a valid token.

Account type: Community (free)

I am the uploader and owner of this asset.

Token: I have created a new token with scope assets:read and All assets selected (see screenshot).

Access attempt:

https://assets.ion.cesium.com/us-east-1/3566183/tileset.json?access_token=MY_TOKEN

(I have tried this with several tokens, all created freshly as “All assets” with the appropriate scope.)

What happens:

I always get this response:

{ "code": "InvalidCredentials", "message": "Invalid access token" }

What I have already tried:

Created multiple new tokens ("All assets" and "Selected assets")

Cleared browser cache and tried different browsers/devices

Confirmed that the asset is listed under "My Assets"

Copied/pasted tokens carefully (no whitespace errors)

Checked the asset ID multiple times

Waited for more than an hour after creating the token

Why does my “All assets” tokens (and asset-specific tokens) do not provide access to my own asset?

Thank you for your help!
Best regards,
uebe

There might be a few points that have to be clarified. But from the description so far, it sounds like there is a misunderstanding about which token is supposed to be used there.

There are two different types of tokens:

  • The “asset token”. This is one of the tokens that are managed at Cesium ion
  • The “<whatever> token”: This is used for accessing the actual tileset

Now… what is <whatever>? There doesn’t seem to be a sensible, agreed-upon name for that. One could call it “tileset token” or “endpoint token”.

Looking at the getAssetEndpoint API function, you will see that this has to receive the “asset token” in its call. But this endpoint also returns a token. Specifically, it returns an “endpoint description” like the following:

{
  "type": "3DTILES",
  "url": "https://assets.cesium.com/23912/tileset.json",
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR...uSOpXwQoaQU_a94",
  ...
}

The accessToken there is the “<whatever>” token. The documentation says

accessToken: The token to be included with each tile request, using Bearer format. This token is different than the access token used throughout the rest of the REST API. It provides access to the asset for approximately one hour. To continue using the asset after the allotted time, simple re-request the endpoint for a new token.

So when you want to access the tileset, then you have to …

  • use the url that was returned there
  • also use the accessToken that was returned there

An aside: Just in case that you are using that fixed string
https://assets.ion.cesium.com/us-east-1/3566183/tileset.json
in your code: Don’t do this. This URL could change at any point in time. That URL may become
https://assets.ion.cesium.com/us-west-2345/3566183/tileset.json
tomorrow. You should always obtain the right url (and token) with the getAssetEndpoint API call.


Several years ago, I created a bunch of “Cesium ion REST API examples”. They are on GitHub, but the repo is private. However, I’ll just dump one of the more basic examples here. It tries to be very elaborate in terms of comments, and the differentiation between the “asset access token”, and the “<whatever> token”, which is referred to as the “tilesetAccessToken” in this snippet:

'use strict';

// For asynchronous requests
const request = require('request-promise');

/**
 * Obtains information about the endpoint that is used for accessing
 * the asset with the given ID.
 *
 * See https://cesium.com/docs/rest-api/#operation/getAssetEndpoint
 *
 * The given access token is one of the access tokens that are managed
 * at https://cesium.com/ion/tokens . The response will contain
 * a new access token that can be used for accessing the tiles of the
 * asset.
 *
 * @param {String} assetAccessToken The asset access token
 * @param {String} assetId The asset ID
 * @returns {Promise<Object>} A promise to the response that contains
 * information about the endpoint and credentials for accessing it
 */
const getAssetEndpoint = async (assetAccessToken, assetId) => {
  return await request({
    url: `https://api.cesium.com/v1/assets/${assetId}/endpoint`,
    method: 'GET',
    headers: { Authorization: `Bearer ${assetAccessToken}` },
    json: true
  });
};

/**
 * Fetch the data from the given URL.
 *
 * The given token is the access token for the TILESET that was returned
 * as part of the asset endpoint info (and it is different from
 * the access token for the ASSET).
 *
 * This is done by issuing a GET request to the given URL. The result
 * is returned as JSON, in compressed form, therefore, the request has
 * to include the "gzip:true" property.
 *
 * @param {String} tilesetUrl The tiles URL
 * @param {String} tilesetAccessToken The access token
 * @returns {Promise<Object>} A promise to the response that contains
 * the tileset JSON
 */
const fetchTileset = async (tilesetUrl, tilesetAccessToken) => {
  return await request({
    url: tilesetUrl,
    method: 'GET',
    headers: { Authorization: `Bearer ${tilesetAccessToken}` },
    gzip: true,
    json: true
  });
};

/**
 * Obtains the access token that was given as the command line parameter.
 * This is the <asset_access_token> that was given at the command line
 * when calling
 *
 *     npm run start <asset_access_token> <asset_id>
 *
 * @returns {String} The access token string
 * @throws If no access token was given
 */
const obtainAssetAccessToken = () => {
  const assetAccessToken = process.argv[2];
  if (!assetAccessToken) {
    throw 'No asset access token was given';
  }
  return assetAccessToken;
};

/**
 * Obtains the asset ID that was given as the command line parameter.
 * This is the <asset_id> that was given at the command line when
 * calling
 *
 *     npm run start <asset_access_token> <asset_id>
 *
 * @returns {String} The asset ID string
 * @throws If no asset ID was given
 */
const obtainAssetId = () => {
  const assetId = process.argv[3];
  if (!assetId) {
    throw 'No asset ID was given';
  }
  return assetId;
};

/**
 * The main function for this example.
 *
 * @throws If there is any error
 */
async function main() {
  const assetAccessToken = obtainAssetAccessToken();
  const assetId = obtainAssetId();

  // Step 1: Obtain endpoint information for the asset
  console.log('Obtaining endpoint for asset ' + assetId);
  const endpointInfo = await getAssetEndpoint(assetAccessToken, assetId);

  // Check the type of the asset. For this example, it must be 3DTILES
  const assetType = endpointInfo.type;
  if (assetType !== '3DTILES') {
    throw (
      'This example shows how to access 3DTILES, but the asset type is ' +
      assetType
    );
  }

  // The endpointInfo contains the URL for the the tileset, as well as
  // a TILESET access token which will be used for accessing the tileset.
  // This token is different from the ASSET access token!
  const tilesetUrl = endpointInfo.url;
  const tilesetAccessToken = endpointInfo.accessToken;

  // Step 2: Fetch the tileset JSON for the asset
  console.log('Obtaining tileset for asset ' + assetId);
  const tileset = await fetchTileset(tilesetUrl, tilesetAccessToken);

  // Print the first part of the resulting JSON, to see whether it makes sense:
  console.log('Resulting tileset.json:');
  const tilesetString = JSON.stringify(tileset, null, 2);
  console.log(tilesetString);
}

/**
 * Print usage information
 */
function printUsage() {
  console.log('The application should be started with');
  console.log('  npm run start <asset_access_token> <asset_id>');
  console.log('where <asset_access_token> is the full access token string.');
}

/**
 * Invoke the main method and print information about any error
 * that may be thrown.
 */
main().catch((err) => {
  printUsage();
  console.log(err);
});

Thank you very much for the detailed and helpful explanation!

Just to make sure I understood correctly:
It’s not possible to use a fixed/static URL for accessing my Cesium ion tileset directly (e.g., in a third-party viewer or application). Instead, only software that actually implements the Cesium ion API (and requests a fresh endpoint token each time) can access the data from my assets on Cesium ion on a long-term basis. Is that correct?

Yes that is correct. You will have to reach out to the API server when you are loading assets to get you temporary credentials and the endpoint to query your asset

Thank you very much for the quick and helpful support.