This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo Simulators REST API

Edit page

Create, inspect, connect to, and stop Expo Simulators sessions from your own systems with the REST API.


The Expo Simulators REST API lets you manage remote device sessions from CI, an agent, or another HTTP client without running EAS CLI. All endpoints live under https://api.expo.dev. Requests and responses are JSON, and successful responses wrap the result in data.

Create a session, poll it until connection details are available, and stop it when you finish. The REST API manages the session lifecycle. Use the selected controller for device actions such as installing an app, pressing a button, or taking a screenshot.

Authentication

All endpoints require an Expo access token, sent as a bearer token in the Authorization header. The account that owns the project must have Expo Simulators access.

For production integrations, create a robot user on the account that owns the project and assign it the Developer role, the minimum role that can create and stop sessions. A personal access token also works for scripts.

Authorization: Bearer <EXPO_TOKEN> Content-Type: application/json

The examples below read the token from the EXPO_TOKEN environment variable. Keep this token in your CI secret store or server environment, not in client code.

Create a session

POST /v2/device-run-sessions

Creates a session and queues the remote device. The request returns before the device and controller are ready.

Request body

Choose at most one application source: buildId, applicationArchiveUrl, or expoGo: true. Providing more than one returns 400. You can omit all three to start with a blank device.

UUID means universally unique identifier.

FieldTypeRequiredDescription
appIdstring (UUID)YesThe EAS project ID. Find it on the project page or in app config under extra.eas.projectId.
platformstringYesandroid or ios.
namestringNoA descriptive session name, up to 255 characters. Must not be blank.
tagsstring[]NoLabels that group sessions, such as one tag for each app variant. Up to 16 tags of 1 to 64 characters each. EAS stores tags in lowercase, trims whitespace, and removes duplicate tags.
typestringNoagent-device, appium, argent, or web-preview-only. Defaults to agent-device. The removed serve-sim type returns 400. Use web-preview-only instead.
packageVersionstringNoVersion of the package for the selected session type. Omit to use the service default.
deviceIdentifierstringNoAndroid virtual device hardware profile, or iOS Simulator name or unique device identifier (UDID). The runner chooses a device when omitted.
maxDurationMinutesintegerNoAutomatic session stop time in minutes. Must be between 0 and 115 inclusive, and within the account's duration limit. The job that runs the session has a hard limit of this value plus five minutes. Omit to use the account maximum.
maxIdleTimeMinutesintegerNoStop after this many minutes without session activity. Must be positive and less than 115. It must be smaller than maxDurationMinutes when both are set. Omit to disable the idle timeout. Not supported for web-preview-only.
buildIdstring (UUID)NoAn EAS Build to download, install, and launch. Use an Android .apk or an iOS Simulator build.
applicationArchiveUrlstringNoAn HTTP or HTTPS URL for an installable application archive, up to 2,048 characters. On iOS, use a .tar.gz or .tgz file that contains the .app. On Android, use an .apk.
expoGobooleanNoSet to true to install and launch Expo Go.
sdkVersionstringNoExpo SDK version used to select Expo Go, such as "58". Only valid with expoGo: true. Omit to use the current Expo Go release for the platform.
launchArgsstring[]NoArguments passed to the installed app when it launches. Requires an application source.
openUrlstringNoA URL to open after launching the app, such as an Expo or development-client URL. Requires an application source.

The application source options install and launch the app for every session type, including appium.

The REST API ignores fields that it does not list here. Build fingerprint matching, local egress, and network capture are available only through EAS CLI: --build-fingerprint, --egress local, and --network-capture.

A web-preview-only session provides the browser preview without a controller. It does not support an idle timeout. Other session types provide a controller and include a browser preview. Sessions created before EAS removed serve-sim still return "type": "serve-sim".

Example

Replace appId and buildId with your EAS project ID and a compatible build ID:

Terminal
- curl -X POST "https://api.expo.dev/v2/device-run-sessions" \
-H "Authorization: Bearer $EXPO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "appId": "a415eac6-231a-4b38-b481-3255a59f13b8", "platform": "ios", "type": "agent-device", "name": "Checkout flow screenshots", "tags": ["checkout"], "buildId": "f9609423-5072-4ea2-a0a5-c345eedf2c2a", "maxDurationMinutes": 30, "maxIdleTimeMinutes": 5 }'

Response

Returns 200 OK with the new session's metadata:

{ "data": { "id": "019d9d17-013a-7e05-89aa-4aa83ff68c32", "appId": "a415eac6-231a-4b38-b481-3255a59f13b8", "url": "https://expo.dev/accounts/acme/projects/example/simulator-sessions/019d9d17-013a-7e05-89aa-4aa83ff68c32", "jobRunId": "019d9d17-1a3f-7c10-bdee-5f2811a9d6ad", "name": "Checkout flow screenshots", "tags": ["checkout"], "type": "agent-device", "packageVersion": null, "buildId": "f9609423-5072-4ea2-a0a5-c345eedf2c2a", "applicationArchiveUrl": null, "platform": "ios", "status": "new", "remoteConfig": null, "maxDurationMinutes": 30, "maxIdleTimeMinutes": 5, "startedAt": null, "finishedAt": null, "createdAt": "2026-08-28T10:00:00.000Z", "updatedAt": "2026-08-28T10:00:00.000Z" } }

Save data.id and set it as SESSION_ID for the remaining examples. url links to the session page on expo.dev. It is not the live browser preview URL.

buildId contains the supplied build ID. applicationArchiveUrl contains the supplied archive URL or the resolved Expo Go archive URL. It is null when using buildId. Optional values such as name, packageVersion, and maxIdleTimeMinutes are null when omitted.

The response's maxDurationMinutes matches the requested session duration. A request for 30 minutes returns 30.

The account's duration limit depends on its plan: 40 minutes on the Free plan and 115 minutes on paid plans. When you omit maxDurationMinutes, the session gets the plan maximum. See Session duration limits.

remoteConfig is null while the session is new, queued, or starting. Poll the get endpoint for status and connection details together.

Get a session

GET /v2/device-run-sessions/:deviceRunSessionId

Returns 200 OK with the same session object as the create endpoint, including its current status, timestamps, and remoteConfig. Replace :deviceRunSessionId with the session ID. No separate connection request is needed.

Example

Terminal
- curl "https://api.expo.dev/v2/device-run-sessions/$SESSION_ID" \
-H "Authorization: Bearer $EXPO_TOKEN"

Response

An agent-device session that is ready returns connection details alongside its metadata:

{ "data": { "id": "019d9d17-013a-7e05-89aa-4aa83ff68c32", "appId": "a415eac6-231a-4b38-b481-3255a59f13b8", "url": "https://expo.dev/accounts/acme/projects/example/simulator-sessions/019d9d17-013a-7e05-89aa-4aa83ff68c32", "jobRunId": "019d9d17-1a3f-7c10-bdee-5f2811a9d6ad", "name": "Checkout flow screenshots", "tags": ["checkout"], "type": "agent-device", "packageVersion": null, "buildId": "f9609423-5072-4ea2-a0a5-c345eedf2c2a", "applicationArchiveUrl": null, "platform": "ios", "status": "in-progress", "remoteConfig": { "agentDeviceRemoteSessionUrl": "https://controller.example.com", "agentDeviceRemoteSessionToken": "<session-token>", "webPreviewUrl": "https://preview.example.com", "webPreviewToken": "<preview-token>" }, "maxDurationMinutes": 30, "maxIdleTimeMinutes": 5, "startedAt": "2026-08-28T10:01:00.000Z", "finishedAt": null, "createdAt": "2026-08-28T10:00:00.000Z", "updatedAt": "2026-08-28T10:01:00.000Z" } }

Session status

The REST API returns lowercase status values:

ValueMeaning
newEAS created the session and has not queued its job yet. remoteConfig is null.
queuedThe session's job waits in the queue for a worker. remoteConfig is null.
startingA worker prepares the device and the controller. remoteConfig is null.
in-progressThe session is running. Connect when remoteConfig is not null.
stoppedThe session has stopped. Terminal. remoteConfig is null.
erroredThe session ended with an error. Terminal. remoteConfig is null.

Poll this endpoint every five seconds until status is in-progress and remoteConfig is not null, then connect using that configuration. Set a timeout in your integration. If the status becomes stopped or errored, stop polling and inspect the session page at url.

Connection details

The fields in remoteConfig depend on the session type. Connection details are only returned while the session is in-progress. After it stops or errors, the session response returns remoteConfig: null even if the session previously had connection details.

Session typeConnection fields
agent-deviceagentDeviceRemoteSessionUrl and agentDeviceRemoteSessionToken. Optional webPreviewUrl and webPreviewToken.
argenttoolsUrl, optional toolsAuthToken, and optional webPreviewUrl and webPreviewToken.
appiumappiumUrl, a capabilities object, and optional webPreviewUrl and webPreviewToken.
web-preview-onlywebPreviewUrl and optional webPreviewToken. This session type has no controller.

Every session type can also include previewApiUrl, the URL of the preview server's API.

For agent-device, use the URL and token as AGENT_DEVICE_DAEMON_BASE_URL and AGENT_DEVICE_DAEMON_AUTH_TOKEN. For Argent, use toolsUrl and toolsAuthToken as ARGENT_TOOLS_URL and ARGENT_AUTH_TOKEN. For Appium, configure your client with appiumUrl and capabilities. See Control a remote simulator with agent-device, Argent, or Appium for controller usage.

Open the browser preview

Open the browser preview URL in a desktop browser. Do not open it on the remote device.

When webPreviewToken is present, the preview is gated, and webPreviewUrl does not work without the token. Add the token to the URL:

  • When the response includes previewApiUrl, webPreviewUrl is a page on expo.dev. Add the token as a URL fragment: <webPreviewUrl>#token=<webPreviewToken>.
  • Otherwise, add the token as a token query parameter: <webPreviewUrl>?token=<webPreviewToken>.

To call previewApiUrl, add the token as a token query parameter. URL-encode the token in each case. EAS CLI builds these URLs for you in eas simulator:get --json output.

Android sessions also include a browser preview that streams the emulator. Select Open preview on the session page on expo.dev to open it in a new tab. Android support is still in development, so some features that work on iOS may not work on Android yet.

Stop a session

POST /v2/device-run-sessions/:deviceRunSessionId/stop

Stops a session. This endpoint does not require a request body.

Example

Terminal
- curl -X POST "https://api.expo.dev/v2/device-run-sessions/$SESSION_ID/stop" \
-H "Authorization: Bearer $EXPO_TOKEN"

Response

Returns 200 OK with the session object. An active session transitions to stopped, includes finishedAt, and returns remoteConfig: null. The operation is idempotent: if the session is already stopped or errored, it returns that session without changing its status or finish time. Connection details remain null.

Always stop the session when your work finishes, including on errors or polling timeouts. Returning from an HTTP request does not stop the remote device. Use a cleanup handler in your integration and set maxDurationMinutes to limit unattended usage.

Errors

StatusReason
400The request fails validation. Examples include an invalid UUID, incompatible application sources, unsupported session options, or a duration outside the account's limit. Session creation also fails if Expo Simulators is not enabled for the account.
401The bearer token is invalid. A request with no Authorization header returns 400 with UNAUTHORIZED_ERROR instead.
403The token does not have permission to read the project or perform the requested action.
404No project or session exists with the given ID.

Use the returned error message to identify which option or permission needs attention. For session startup failures, inspect the session page linked by url.

Next step

Install an app on a remote Android Emulator or iOS Simulator

Choose a compatible build, connect a controller, and interact with the remote device.