Troupeby AliveDocsSign in

Integrate

Calling the API directly

Every Troupe operation is an HTTP request that you can send yourself, from any language. This page covers the REST and RPC paths, authentication, the contract header and errors.

In TypeScript, the client SDK does all of this for you.

One contract, three bindings

The runtime's contract has 64 procedures in 14 groups. Each has an id, <group>.<operation>, and every other name follows from it:

BindingRuleFor input.doubleTap
REST/v1/<group>/<operation in kebab-case>POST /v1/input/double-tap
RPC/rpc/v2/<group>/<operation>/rpc/v2/input/doubleTap
MCP toolThe id with _ for the dotinput_doubleTap

The reference has a page per group, with each procedure's route, scopes, input, output and errors. A local runtime and Troupe Cloud serve the same contract:

ServerAddress
A local runtimeThe url in $TROUPE_HOME/runtime.json: http://127.0.0.1:<port>
Troupe Cloudhttps://api.troupe.ali.ve

REST

  • Method. GET when the procedure only reads and its input is flat: scalars, enums and arrays of scalars. POST otherwise. No other method is used.
  • Input. A GET takes its input as query parameters, with arrays in bracket notation (sources[0]=app). A POST takes it as a JSON body. There are no path parameters: ids travel in the input.
  • Output. JSON, except for two kinds. A binary procedure, such as media.screenshot, answers with the raw bytes and their Content-Type. An event iterator, such as logs.stream, answers with text/event-stream.

Read the address and the owner token of a local runtime, then call it:

Shell
URL=$(jq -r .url ~/.troupe/runtime.json)
TOKEN=$(jq -r .token ~/.troupe/runtime.json)

curl -X POST "$URL/v1/sessions/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Troupe-Contract: 1.0" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"platform":"ios-simulator"}}'

A read is a GET with a query:

Shell
curl "$URL/v1/sessions/get?sessionId=ses_01jbx3y5m7c9tq2v4w6x8z0a1b" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Troupe-Contract: 1.0"

A screenshot is a binary answer:

Shell
curl -X POST "$URL/v1/media/screenshot" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"ses_01jbx3y5m7c9tq2v4w6x8z0a1b"}' \
  --output screen.png

A request body may be at most 4 MiB. Inputs are strict: a key the procedure does not define fails with VALIDATION.

Event streams

An event iterator stays open and sends one server-sent event per item, each a JSON object with a seq.

Shell
curl --no-buffer "$URL/v1/logs/stream?sessionId=ses_01jbx3y5m7c9tq2v4w6x8z0a1b" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: text/event-stream"

The stream ends with event: done. A failure arrives as event: error whose data is the error body. To resume after a drop, pass the last seq you saw as since. If the server no longer has the events after it, the first event is a gap.

Read these streams with a streaming HTTP client and the bearer header. A browser's EventSource cannot send the header.

RPC

/rpc/v2/<group>/<operation> serves the same procedures in the wire format of oRPC's RPC protocol, for a caller that already uses an oRPC client. v2 is the version of that wire format, not of the contract. Inputs, outputs, scopes and errors are the same as on REST. Troupe's own client and CLI use REST.

Authentication

Send Authorization: Bearer <token> on every call. Two procedures are public and ignore it: runtime.version and pairings.redeem.

ServerCredentialWhere it comes from
A local runtimeThe owner token, tr_rt_…runtime.json. It holds every scope and changes when the daemon restarts
A local runtimeA pairing token, tr_pt_…A paired browser page. It works from one origin only
Troupe CloudAn API key, tr_key_…API keys
Troupe CloudAn access tokentroupe login, or a token exchange in CI

Each procedure lists the scopes it needs, and the caller must hold all of them.

ScopeGrants
runtime:readThe runtime's health, host and environment checks
runtime:adminStopping the runtime, sweeping leaks, managing pairings
devices:readObserving: sessions, the accessibility tree, screenshots, watch tickets
devices:acquireCreating, keeping alive and releasing sessions
devices:driveInput, system settings, launching apps, drive tickets
apps:installInstalling, uninstalling and resetting apps, and their permissions
logs:readLogs and crash reports
runs:read, runs:reportReading test runs, and publishing one

A missing, invalid or expired credential is UNAUTHENTICATED (401). A valid one without the scopes is FORBIDDEN (403), and its data.requiredScopes names every scope the call needs.

A local runtime also checks where a request comes from:

  • The Host header must be 127.0.0.1:<port> or localhost:<port>.
  • An Origin header, which browsers send, must be the runtime's own origin or a paired one.
  • Requests without a valid credential share a budget of 600 a minute. Past it the answer is RATE_LIMITED.

Headers

HeaderDirectionMeaning
AuthorizationRequestBearer <token>
Troupe-ContractBothThe contract version, 1.0: yours on a request, the server's on a response
Troupe-Request-IdBothA correlation id. The server echoes yours or mints one (req_…). Quote it when you report a problem
Troupe-Call-IdRequestThe id under which the runtime records this call in a trace
Retry-AfterResponseWhole seconds, on a 429 or 503 that says when to try again
Troupe-ProjectRequestTroupe Cloud: the project to act in, among those the credential can see

The contract version

The contract's version is major.minor, now 1.0. The path carries the major (/v1). Within a major, changes only add: new procedures, new optional input fields, new output fields and new enum values. So a client must ignore output fields and enum values it does not know.

A request whose Troupe-Contract names a major the server does not serve fails with CONTRACT_UNSUPPORTED (400). runtime.version needs no credential and says which version and majors a server serves:

Shell
curl "$URL/v1/runtime/version"

Errors

Every failure has the same body, on every binding:

JSON
{
  "code": "NOT_FOUND",
  "message": "Not found.",
  "data": { "kind": "session", "id": "ses_01jbx3y5m7c9tq2v4w6x8z0a1b" },
  "retryable": false,
  "requestId": "req_…"
}

Decide on code, never on the message. data is typed per code; Errors lists every code with its data and status.

StatusCodes, for example
400VALIDATION, CONTRACT_UNSUPPORTED
401, 403UNAUTHENTICATED, FORBIDDEN
404NOT_FOUND: unknown, expired, or not visible to the caller
408TIMEOUT
409CONFLICT, SESSION_NOT_READY, SESSION_BUSY, APP_NOT_RUNNING
410SESSION_ENDED
422CAPABILITY_UNSUPPORTED, DEVICE_SPEC_INVALID, APP_CRASHED, LOCATOR_NOT_FOUND
429RATE_LIMITED, and QUOTA_EXCEEDED in Troupe Cloud
500INTERNAL
503CAPACITY_UNAVAILABLE, DEVICE_BOOT_FAILED, every ENVIRONMENT_* code
  • retryable: true means the same request may succeed later. Wait for data.retryAfterMs, or the Retry-After header, when there is one.
  • An ENVIRONMENT_* error carries data.fix: one command or instruction that repairs the machine.
  • A code you do not know comes from a newer server. Treat it by its status.

Conventions

  • Ids are a prefix and 26 lower-case characters, such as ses_… for a session. An id is a handle, not a credential: every call is still authorized.
  • Timestamps are ISO 8601 strings in UTC. Durations are integers in milliseconds, named …Ms.
  • Coordinates are in points, in screen space for the current orientation, from the top left.
  • Lists take limit and cursor and return nextCursor.

Next

esc

    Type to search every page, the protocol reference and the cloud API.