This is the documentation of version 1.0.0-rc.3. Go to the latest version, 1.0.0-rc.5.

Request options and errors

Every service call takes the same options on top of its own parameters: request validation, overrides of the global configuration and a signal to cancel it. Each call runs these steps in order:

  1. An already-aborted signal rejects at once, before any work.
  2. The global configuration is merged under the call's parameters.
  3. The resulting commonBaseURL is checked, even with validateRequest: false. An insecure one rejects with INSECURE_BASE_URL.
  4. The parameters are validated, unless validateRequest: false.
  5. The request is built.
  6. The request is sent, and a 429 is retried as the call's and the global retry set.
  7. The response is parsed.

Request validation

Before sending, the SDK checks the parameters against the service's schema. A malformed call fails early with a message in the SDK's terms, and never reaches the API or your usage quota.

import { discoverPlaces } from '@tomtom-org/maps-sdk/services';

await discoverPlaces({ query: 'Eiffel Tower', validateRequest: false });

Per-call overrides

You can pass the API key, language, geopolitical view, base URL and retry behaviour of the global configuration to a single call instead. They override the global values for that call only:

// French for this call only; later calls keep the global language
await discoverPlaces({ query: 'restaurants', language: 'fr-FR' });

// Give up on a rate-limited search after 2 seconds; the global wait and factor still apply
await getSearchSuggestions({ query: 'coffee', geoBias: { position: [4.9041, 52.3676] }, retry: { timeoutMs: 2000 } });

Cancelling a request

Every call accepts a signal, an AbortSignal. Aborting it cancels the HTTP request and rejects the call with an SDKAbortError, whose name is 'AbortError'.

Keeping only the latest request

When a new request supersedes an older one (a click lookup, a search-as-you-type box, a search re-run on every map move), a slow earlier response can land after a faster later one and overwrite it.

createLatestRequest holds one such request at a time. Each run aborts the one before it and hands your callback a fresh signal. cancel aborts without starting another:

import { createLatestRequest, reverseGeocode } from '@tomtom-org/maps-sdk/services';

const latestLookup = createLatestRequest();

map.mapLibreMap.on('click', async (event) => {
    const lookup = await latestLookup.run((signal) =>
        reverseGeocode({ position: event.lngLat.toArray(), signal })
    );
    if (!lookup.current) return; // a newer click or a cancel retired it
    showAddress(lookup.value);
});

clearButton.addEventListener('click', latestLookup.cancel);

Deadlines

There is no timeout option. Instead, compose a deadline onto the signal, alone or with your own cancellation:

import { calculateRoute, createLatestRequest, getSearchSuggestions } from '@tomtom-org/maps-sdk/services';

// A short deadline for a search box
await getSearchSuggestions({
    query: 'coffee',
    geoBias: { position: [4.9041, 52.3676] },
    signal: AbortSignal.timeout(3000)
});

// A longer one for a route, still retired by the next run
const latestRoute = createLatestRequest();
const route = await latestRoute.run((signal) =>
    calculateRoute({
        locations: [[4.9041, 52.3676], [2.3522, 48.8566]],
        signal: AbortSignal.any([signal, AbortSignal.timeout(15000)])
    })
);

Error handling

A service call rejects with an SDKError. A request the service can't build at all, such as a geometry type it does not search sent with validateRequest: false, rejects with a plain Error instead. An SDKError's code says what went wrong, service names the service that failed as a ServiceName such as 'Geocode' or 'ReachableRange', and sdkVersion gives the SDK version for bug reports:

code When
INVALID_REQUEST The SDK's request validation failed (issues lists why), or the API answered 400 or another 4xx not listed below
INSECURE_BASE_URL commonBaseURL is neither https: nor http: on a loopback address
INVALID_CREDENTIALS The API answered 401, because the key is missing, invalid or expired
INSUFFICIENT_PERMISSIONS The API answered 403, because the key is valid but not allowed this request. See A key refused access
NOT_FOUND The API answered 404
RATE_LIMITED The API still answered 429 after the retries
LIVE_SERVICE_NOT_AVAILABLE The API answered a 5xx
NETWORK_NOT_AVAILABLE No response arrived: offline, DNS, TLS or CORS failure. cause holds what fetch threw
ABORTED The signal was aborted. cause holds the abort reason
INTERNAL Anything else, such as a response the SDK could not read

Two subclasses carry more:

Error When Carries
SDKServiceError The API answered with an error status status, apiErrorCode (the API's own name for the error, when it gave one) and privatePreviewAccess
SDKAbortError The signal was aborted cause, the abort reason

discoverOnePlace and geocodeOne also throw a plain Error when nothing matches the query. To get an empty result instead, use discoverPlaces or geocode.

import { discoverPlaces, SDKError } from '@tomtom-org/maps-sdk/services';

try {
    const results = await discoverPlaces({
        query: 'coffee',
        geoBias: { position: [4.9041, 52.3676] },
        signal: AbortSignal.timeout(5000)
    });
} catch (error) {
    if (!(error instanceof SDKError)) throw error;

    switch (error.code) {
        case 'ABORTED':
            console.error('Search timed out');
            break;
        case 'INVALID_CREDENTIALS':
            console.error('Check the API key');
            break;
        case 'INSUFFICIENT_PERMISSIONS':
            console.error(`Check the APIs and domains the key allows: ${error.message}`);
            break;
        case 'RATE_LIMITED':
            console.error('Rate limited, even after retrying');
            break;
        case 'NETWORK_NOT_AVAILABLE':
            console.error('No connection');
            break;
        default:
            console.error(`${error.service} failed (${error.code}, SDK ${error.sdkVersion}): ${error.message}`);
    }
}

The code below geocodes a batch of addresses in parallel under one deadline, and reads each failure from Promise.allSettled by its code and canRetry. Swap the latitude-first position for [139.76, 35.68] and that address resolves:

A key refused access

A 401 and a 403 are two different problems. INVALID_CREDENTIALS means the API doesn't know the key. INSUFFICIENT_PERMISSIONS means the key is valid but may not make this request. apiErrorCode names the cause of a 403:

apiErrorCode Cause Fix
'Forbidden' The API is not enabled for the key Enable it for the key, or for a Private Preview API, request access
'InvalidReferer' The key's domain restriction refused the page Add the page's domain to the key, or set a fixed referrer from your proxy

A Private Preview API, such as Calculate Reachable Range, needs access granted to the key. The Freemium and Pay As You Grow plans don't include it. When such an API answers 'Forbidden', or a proxy answers a 403 with no body, the error says so in two places:

import { calculateReachableRange, SDKServiceError } from '@tomtom-org/maps-sdk/services';

try {
    await calculateReachableRange({ origin: [4.9041, 52.3676], budget: { type: 'timeMinutes', value: 30 } });
} catch (error) {
    if (error instanceof SDKServiceError && error.privatePreviewAccess) {
        const { product, requestAccessURL } = error.privatePreviewAccess;
        showMessage(`${product} needs access on your API key: ${requestAccessURL}`);
    } else throw error;
}

Related guides and examples

Related guides

Related examples