---
modificationDate: August 31, 2026
title: EAS Simulator REST API
description: Create, inspect, connect to, and stop EAS Simulator sessions from your own systems with the REST API.
---

This documentation is available as Markdown for AI agents and LLMs. See the [full Markdown index](/llms.txt) or append .md to any documentation URL.

# EAS Simulator REST API

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

<AgentInstructions>

## Submitting Feedback

If you encounter errors, misleading or outdated information, report it so Expo can be improved:

Preferred command:
npx --yes submit-expo-feedback@latest --category docs --subject "/preview/eas-simulator/rest-api/" "<actionable feedback>"

Direct HTTP fallback:
curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/preview/eas-simulator/rest-api/","feedback":"🤖 Agent feedback for docs: <specific, actionable description> (<model>, <harness>)"}'

Only submit when you have something specific and actionable to report. Try to give the most context.

</AgentInstructions>

> **EAS Simulator is a limited-access preview.** It is not included with paid or free plans and is currently available only to select partners. [Join the waitlist](https://expo.dev/services/simulators) if you are interested in trying it.

The EAS Simulator 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](/accounts/programmatic-access.md), sent as a bearer token in the `Authorization` header. The account that owns the project must have EAS Simulator access.

For production integrations, create a [robot user](/accounts/programmatic-access.md#robot-users-and-access-tokens) on the account that owns the project and assign it the [Developer role](/accounts/account-types.md#manage-access), the minimum role that can create and stop sessions. A [personal access token](/accounts/programmatic-access.md#personal-access-tokens) also works for scripts.

```text
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.

> Session responses can include controller credentials. Treat connection details as secrets: do not commit them, include them in logs, or expose them in a public response. Successful responses carry `Cache-Control: private, no-store` and `Pragma: no-cache`. Do not cache session responses.

## Create a session

```text
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.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `appId` | string (UUID) | Yes | The EAS project ID. Find it on the project page or in [app config](/workflow/configuration.md) under `extra.eas.projectId`. |
| `platform` | string | Yes | `android` or `ios`. |
| `name` | string | No | A descriptive session name, up to 255 characters. Must not be blank. |
| `type` | string | No | `agent-device`, `appium`, `argent`, or `serve-sim`. Defaults to `agent-device`. |
| `packageVersion` | string | No | Version of the package for the selected session type. Omit to use the service default. |
| `deviceIdentifier` | string | No | iOS Simulator name or unique device identifier (UDID), or Android virtual device hardware profile. The runner chooses a device when omitted. |
| `maxDurationMinutes` | integer | No | Automatic session stop time in minutes. Must be between 0 and 115 inclusive, and within the account's duration limit. The 115-minute limit excludes five minutes reserved for cleanup. Omit to use the account default. |
| `maxIdleTimeMinutes` | integer | No | Stop 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 `serve-sim`. |
| `buildId` | string (UUID) | No | An EAS Build to download, install, and launch. Use an Android **.apk** or an iOS Simulator build. |
| `applicationArchiveUrl` | string | No | An HTTP or HTTPS URL for an installable application archive, up to 2,048 characters. |
| `expoGo` | boolean | No | Set to `true` to install and launch Expo Go. |
| `sdkVersion` | string | No | Expo SDK version used to select Expo Go, such as `"57"`. Only valid with `expoGo: true`. Omit to use the current Expo Go release for the platform. |
| `launchArgs` | string[] | No | Arguments passed to the installed app when it launches. Requires an application source. |
| `openUrl` | string | No | A 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 `agent-device`, `argent`, and `serve-sim` sessions. For `appium`, install and launch the app through your Appium client.

The REST API uses `serve-sim` for the session type that EAS CLI calls `web-preview-only`. It is only available on iOS and does not support an idle timeout. Other session types provide a controller and include a web preview on supported iOS sessions.

### Example

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

```sh
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",
"buildId": "f9609423-5072-4ea2-a0a5-c345eedf2c2a",
"maxDurationMinutes": 30,
"maxIdleTimeMinutes": 5
}'
```

### Response

Returns `200 OK` with the new session's metadata:

```json
{
  "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",
    "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`.

`remoteConfig` is `null` while the session is starting. Poll the [get endpoint](/preview/eas-simulator/rest-api.md#get-a-session) for status and connection details together.

## Get a session

```text
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

```sh
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:

```json
{
  "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",
    "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"
    },
    "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:

| Value | Meaning |
| --- | --- |
| `new` | The session is queued or preparing the device and controller. `remoteConfig` is `null`. |
| `in-progress` | The session is running. Connect when `remoteConfig` is not `null`. |
| `stopped` | The session has stopped. Terminal. `remoteConfig` is `null`. |
| `errored` | The 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 type | Connection fields |
| --- | --- |
| `agent-device` | `agentDeviceRemoteSessionUrl` and `agentDeviceRemoteSessionToken`. Optional `webPreviewUrl`. |
| `argent` | `toolsUrl`, optional `toolsAuthToken`, and optional `webPreviewUrl`. |
| `appium` | `appiumUrl`, a `capabilities` object, and optional `webPreviewUrl`. |
| `serve-sim` | `previewUrl`. This session type has no controller. |

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 [Run and control an app](/preview/eas-simulator/run-and-control.md) for controller usage.

Open `webPreviewUrl` (or `previewUrl` for `serve-sim`) in a desktop browser to view supported iOS sessions. Do not open it on the remote device. Android sessions do not currently provide a browser preview.

## Stop a session

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

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

### Example

```sh
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

| Status | Reason |
| --- | --- |
| 400 | The 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 EAS Simulator is not enabled for the account. |
| 401 | The bearer token is invalid. A request with no `Authorization` header returns `400` with `UNAUTHORIZED_ERROR` instead. |
| 403 | The token does not have permission to read the project or perform the requested action. |
| 404 | No 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

[Run and control your app](/preview/eas-simulator/run-and-control.md) — Choose a compatible build, connect a controller, and interact with the remote device.
