Getting Started with the TypeScript API Client
The Spaces API client (@zwspaces/api-client) is a TypeScript library for the Spaces REST API. It handles authentication, retries and errors for you, and comes with types for every Space, entity, floor and tag, so your editor can autocomplete fields and catch mistakes as you write.
It works anywhere fetch is available: in the browser, in Node.js 18 or later, and in edge runtimes.
Before you start
Section titled “Before you start”You’ll need:
- The API endpoint for your Space. This can vary depending on where your Space’s data is stored. The Zappar team can give you the right endpoint.
- A Zapworks API key, user token or OIDC sign-in, unless you only need to read public Spaces. See Authentication below.
- The ID of your Space. This is the project ID of the Space in Zapworks.
The API client isn’t published to the public npm registry yet. Get in touch and we’ll give you access.
Creating a client
Section titled “Creating a client”Create a SpacesClient with your endpoint and token, then get a handle to your Space:
import { SpacesClient } from '@zwspaces/api-client';
const client = new SpacesClient({ endpoint: 'https://<your-api-endpoint>', token: process.env.ZAPWORKS_API_KEY,});
const space = client.space('<your-space-id>');Creating the handle doesn’t make a request. Requests are made when you call a method on it, like space.get().
Authentication
Section titled “Authentication”The token option takes a Zapworks API key or a user token. There are three ways to get one:
- A Zapworks API key, for code that runs on your server.
- A token from your own identity provider (OIDC), if you have OIDC set up with Zapworks. See Authenticating with OIDC below.
- A Zapworks user token, for calling the API from a browser as one of your users. This needs an OAuth 2.0 application registered with the Zappar team. Get in touch to set this up.
Keep API keys on your server. Never include them in code that runs in a browser, where anyone can read them.
If your token expires, pass a function instead of a string. The client calls it before every request, so it can return a fresh token:
const client = new SpacesClient({ endpoint: 'https://<your-api-endpoint>', token: async () => await getFreshToken(),});Authenticating with OIDC
Section titled “Authenticating with OIDC”If your organization’s identity provider is set up with Zapworks through OIDC, you can exchange the token your identity provider gives you (usually an ID token) for a short-lived Zapworks token with client.exchangeToken(), which calls the API’s token exchange operation.
Zapworks tokens from an exchange expire, so the token function below keeps the current one and exchanges again shortly before it runs out:
import { SpacesClient } from '@zwspaces/api-client';
let current: { token: string; expiresAt: number } | undefined;
const client = new SpacesClient({ endpoint: 'https://<your-api-endpoint>', token: async () => { // Exchange again if there's no token yet, or it expires within the next minute if (!current || Date.now() > current.expiresAt - 60_000) { const idToken = await getIdTokenFromYourProvider(); const result = await client.exchangeToken(idToken); // If the response doesn't say how long the token lasts, assume 5 minutes current = { token: result.access_token, expiresAt: Date.now() + (result.expires_in ?? 300) * 1000, }; } return current.token; },});getIdTokenFromYourProvider() stands for however your app gets a token from your identity provider, such as its sign-in library. The ID token must still be valid when you exchange it.
exchangeToken() expects an ID token. If you have a different kind of token, pass its type as the second argument: 'urn:ietf:params:oauth:token-type:jwt' or 'urn:ietf:params:oauth:token-type:access_token'.
Token exchange only works with identity providers that Zapworks has been set up to trust. Get in touch to set up OIDC for your organization.
Reading and updating a Space
Section titled “Reading and updating a Space”space.get() returns the whole Space, including its entities, floors and tags:
const doc = await space.get();console.log(doc.name);Updates are merge patches: only the fields you include change, and setting a field to null removes it.
await space.update({ name: { value: 'Head Office', locale: 'en' }, subtitle: null,});Working with entities
Section titled “Working with entities”Entities are the things placed in a Space, such as landmarks, doors and obstacles. Each one has a type. The entities collection lets you list, create, update and delete them:
// Every landmark in the Spaceconst landmarks = (await space.entities.list()).filter((e) => e.type === 'landmark');
// Add a door, hide it, then remove itconst door = await space.entities.create({ type: 'door', pose: { position: [4, 0, 2], quaternion: [0, 0, 0, 1] },});await space.entities.update(door.id, { hidden: true });await space.entities.delete(door.id);Floors (space.floors) and tags (space.tags) work the same way.
Changes you make through the API are merged in real time with changes made in the Spaces web app. They reach visitors to a public Space within about 30 seconds.
Planning a route
Section titled “Planning a route”Ask for a route between two points in the Space. Each point can be a position, as [x, y, z] coordinates in metres, or the ID of an entity such as a landmark.
const route = await space.route([2, 0, 3], '<landmark-id>', { stepFree: true });
console.log(route.summary.distance); // in metresconsole.log(route.path); // the points to followstepFree asks for a route without steps or stairs, if there is one. Check route.stepFree to see whether you got one. Use avoidElevators to take elevators only when there’s no other way.
Reading public Spaces without a token
Section titled “Reading public Spaces without a token”Visitor-facing apps usually don’t need a token to read a public Space. Leave out token and add the publicEndpoint that published Spaces are served from. The Zappar team can give you this along with your API endpoint.
const client = new SpacesClient({ endpoint: 'https://<your-api-endpoint>', publicEndpoint: 'https://<your-public-endpoint>',});
const space = client.space('<your-space-id>');const doc = await space.get();const route = await space.route([2, 0, 3], '<landmark-id>');Without a token, you can read a public Space, its settings, entities, floors and tags, and plan routes through it. Reads come from the Space as it was last published, so they can be up to about 30 seconds behind the latest edits. Anything that changes a Space needs a token.
To find public Spaces near a location, use client.discover(). Only the rough area around the position is sent to the server, and results come back nearest first:
const nearby = await client.discover({ latitude: 51.5072, longitude: -0.1276 });Handling errors
Section titled “Handling errors”When the API returns an error, the client throws a SpacesApiError. Its code tells you what went wrong, for example notFound, invalidBody or notPublic:
import { SpacesApiError } from '@zwspaces/api-client';
try { await space.entities.get('missing');} catch (err) { if (err instanceof SpacesApiError && err.code === 'notFound') { // The entity doesn't exist } else { throw err; }}The error also has the HTTP status (0 if no response arrived). For invalidBody errors, issues lists what failed validation.
If you hit a rate limit (429) or the service is briefly unavailable (503), the client waits as long as the server asks and tries again, up to 3 times. You can change this with the maxRetries option.
Next steps
Section titled “Next steps”The client also covers files, commits, public access, the navigation mesh and IMDF import and export. Your editor will show what’s available on space as you type. For details of each operation and the fields it accepts, see the REST API reference.