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
- A paid plan, with a payment method for compute and a spend limit you are happy with.
- A project in the Editor, and its project ID.
- A client key in the app, and a secret key with the
vpsscope on your backend.- User tokens (minted by your backend) if your app's users record their own scans.
- ViroReact 3.1.0 or later, with
rvApiKeyandrvProjectIdin the Expo plugin.depthCaptureEnabledonViroARSceneNavigatorif 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 403COMPUTE_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_REACHEDuntil 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.
| Caller | Credential | Can do |
|---|---|---|
| Your app, tracking shared places | Client key (rv_client_), as rvApiKey | Find 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 scans | User token (rv_ut_) with the vps scope, minted by your backend | Create, upload, list and download that user's own scans and locations, and read the shared ones. |
| Your backend | Secret key (rv_secret_) with the vps scope | Everything: 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/serverrefuses a client key with 403API_KEY_TYPE_NOT_ALLOWED, and a client key that sends an app user id gets 403APP_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 403API_KEY_SCOPE_NOT_ALLOWED. A backend that only mints user tokens can hold a key with thevpsscope 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). scopesis optional. Left out, the token carries every token scope (colocation,vps) the key holds. An operation outside the token's scopes is refused with 403APP_USER_TOKEN_SCOPE_NOT_ALLOWED.app_user_idis 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_COMPLETEDandFOREGROUND_SERVICEto 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,depthEnabledandworldMeshEnabledask 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
Updated about 3 hours ago