HomeGuidesAPI ReferenceChangelogDiscussions
Hire a ReactVision Expert 🛠️Launch Studio 🖥️Sponsor on GitHub ❤️Log In
Guides

Setting Up Advanced VPS

Plan and billing, project, keys and user tokens, the Expo plugin and depth capture.

Before your app can scan and track places, set up five things: a paid plan with compute billing, a project, the right credentials, the ViroReact Expo plugin and depth capture.

📘

Checklist

  1. A paid plan, with a payment method for compute and a spend limit you are happy with.
  2. A project in the Editor, and its project ID.
  3. A client key in the app, and a secret key with the vps scope on your backend.
  4. User tokens (minted by your backend) if your app's users record their own scans.
  5. ViroReact 3.1.0 or later, with rvApiKey and rvProjectId in the Expo plugin.
  6. depthCaptureEnabled on ViroARSceneNavigator if you want depth in your scans.

1. A paid plan and compute billing

Creating locations and scans and uploading recordings need a paid plan. Without one they answer 403 PAID_PLAN_REQUIRED. Server-mode localisation needs a paid plan too.

Advanced VPS is billed as compute time. In the Editor:

  • Payment method. The team's plan needs a payment method that can be charged for compute time, otherwise Advanced VPS is refused with 403 COMPUTE_BILLING_NOT_SET_UP. An unpaid compute invoice gives 403 COMPUTE_PAYMENT_FAILED.
  • Spend limit. Owners and admins set the monthly compute spend limit on the team's Usage page. The default is $100. At the limit, Advanced VPS answers 403 COMPUTE_SPEND_LIMIT_REACHED until the next usage period.
  • Included credit. $5 a month on Basic and $15 on Pro. Usage resets on your plan's renewal date.
  • Scans per app user. An app user may start 10 scans a day (UTC) unless the team sets its own limit in the Editor.

Scan processing and server-mode localisation cost $0.60 a minute each, and only scans whose map is accepted are billed. See Pricing.


2. A project

Create a project in the Editor and copy its project ID, as in Getting Started with ReactVision Spatial. Locations and scans belong to one project, and every API call names it in x-project-id.


3. Keys and user tokens

Which credential you need depends on what the caller does.

CallerCredentialCan do
Your app, tracking shared placesClient key (rv_client_), as rvApiKeyFind and track the project's shared locations, and download the files devices localise with (map, world_mesh, semantic). Nothing else.
Your app, for a signed-in user who scansUser token (rv_ut_) with the vps scope, minted by your backendCreate, upload, list and download that user's own scans and locations, and read the shared ones.
Your backendSecret key (rv_secret_) with the vps scopeEverything: every location and scan of the project, every file (including mesh, splat and poses), deleting, and minting user tokens. With x-app-user-id, it acts for one app user.

🚧

A client key cannot create or upload scans

Every Advanced VPS operation except reading locations, downloading their files and GET /vps/server refuses a client key with 403 API_KEY_TYPE_NOT_ALLOWED, and a client key that sends an app user id gets 403 APP_USER_ID_NOT_ALLOWED. To scan from your app, give it a user token. Never put a secret key in an app you publish.

Create the keys

Create keys in the Editor on your team's API Keys page (in the sidebar, under Integrations):

  • Client key: bound to your project. See Client Keys.
  • Secret key: give it the Advanced VPS (vps) scope, or full access. A key created without it is refused with 403 API_KEY_SCOPE_NOT_ALLOWED. A backend that only mints user tokens can hold a key with the vps scope alone. Scopes cannot be changed later: create a new key instead. See Secret Keys and Scopes.

Mint user tokens on your backend

Your backend signs the user in, then mints a token for them with its secret key (Mint a user token) and returns the answer to the app:

// Your backend, for example an Express route behind your own sign-in.
const r = await fetch("https://platform.reactvision.xyz/functions/v1/app-user-tokens", {
  method: "POST",
  headers: {
    "x-api-key": process.env.RV_SECRET_KEY!,
    "content-type": "application/json",
  },
  body: JSON.stringify({
    project_id: process.env.RV_PROJECT_ID,
    app_user_id: user.id, // your own id for the signed-in user
    scopes: ["vps"],
  }),
});
const minted = await r.json(); // { token, token_type, expires_at, expires_in, scopes, meta }
res.json(minted);
  • A token reaches one project and one app user, and lasts up to one hour (ttl_seconds, 60 to 3600).
  • scopes is optional. Left out, the token carries every token scope (colocation, vps) the key holds. An operation outside the token's scopes is refused with 403 APP_USER_TOKEN_SCOPE_NOT_ALLOWED.
  • app_user_id is 1 to 255 printable ASCII characters, with no spaces, and not . or ...
  • Minting is limited to 1,200 tokens a minute per key, and 60 an hour per key and app user, so cache a token per user until shortly before it expires.
  • Deleting the secret key that minted a token revokes the token.

In the app, createUserTokenCredentials() asks your backend for a token only when it needs one. See Acting for a signed-in user.


4. ViroReact and the Expo plugin

Advanced VPS needs ViroReact 3.1.0 or later. These docs are for 3.1.1. A scene placed in a location in the Editor also needs 3.1.0 or later: older versions are refused with 426 SCENE_REQUIRES_VPS_RUNTIME.

npm install @reactvision/[email protected]

Add your project's client key and project ID to the ViroReact Expo plugin in app.json, then run npx expo prebuild again:

{
  "expo": {
    "plugins": [
      [
        "@reactvision/react-viro",
        {
          "rvApiKey": "YOUR_REACTVISION_CLIENT_KEY",
          "rvProjectId": "YOUR_REACTVISION_PROJECT_ID"
        }
      ]
    ]
  }
}

Without Expo, set RVApiKey and RVProjectId in Info.plist, and com.reactvision.RVApiKey and com.reactvision.RVProjectId in AndroidManifest.xml, as in Integrating ViroReact with React Native (no Expo).

With those set, <ViroVPS>, uploadScanRecording() and the location functions need no address or credentials. They use the Editor session if there is one, else rvApiKey and rvProjectId, and https://platform.reactvision.xyz. Pass credentials to use a user token instead.

What the upload adds to your app

  • Android: the background upload runs in a WorkManager job, which adds WAKE_LOCK, ACCESS_NETWORK_STATE, RECEIVE_BOOT_COMPLETED and FOREGROUND_SERVICE to the merged manifest. Before Android 12 the job shows a low-importance "Uploading scan" notification.
  • iOS: the upload runs in a background URLSession. Optionally, call [VRTVPSUploadSession handleEventsForBackgroundURLSession:completionHandler:] from your app delegate so an upload that finished while the app was gone is completed in that background launch. Without it, ReactVision Spatial completes it within a minute.

5. Depth capture

Depth helps blank walls and plain surfaces reconstruct, so it improves the mesh. Set depthCaptureEnabled on the navigator from the moment it mounts:

<ViroARSceneNavigator depthCaptureEnabled initialScene={{ scene: ScanScene }} />
  • Switching depth on reconfigures the AR session, which can make tracking jump, so it is not turned on when recording starts.
  • It is combined with what occlusionMode, depthEnabled and worldMeshEnabled ask for, and never turns their depth off.
  • It needs LiDAR on iOS or the ARCore Depth API on Android. Elsewhere it has no effect and recordings are made without depth.

Ask for depth per recording with startRecording(outputDir, { depth: true }). See Use Advanced VPS in your app.


Next steps