Troupe Cloud
API keys
An API key is a scoped credential that belongs to an organization, for CI and other callers with nobody at the keyboard. This page covers creating one, choosing its scopes, restricting it to a project, using it, and rotating and revoking it.
Create a key
Owners and admins create keys on the organization's API keys page in the web app.
| Field | Meaning |
|---|---|
| Name | What the key list shows, up to 100 characters |
| Purpose | Optional. A short slug that groups keys for rotation; the name's slug when left out |
| Expires in (days) | 90 by default |
| Project | Every project of the organization, or one |
| Scopes | What the key may do, at most what your own role allows |
Creating a key needs a password confirmation. The secret,
tr_key_ followed by 43 characters, is shown once. Troupe keeps only a hash of it, so store it in your CI
system's secret store before you close the dialog. Every owner of the organization is mailed when a key is
created or revoked.
A key belongs to the organization, not to the person who made it: it keeps working when that person leaves.
Over the API, keys.create takes a signed-in person only. A key cannot create another key.
Scopes
A key carries the scopes you tick and nothing else. The form starts with the seven device and test scopes ticked.
| Scope | Lets the key |
|---|---|
devices:read | See sessions, the accessibility tree and screenshots; get a ticket to watch a stream |
devices:acquire | Start, keep alive and release device sessions |
devices:drive | Tap, type, launch apps, open URLs, change device settings; get a ticket to drive a stream |
apps:install | Install, uninstall and reset apps, and set their permissions |
logs:read | Read device logs and crash reports |
runs:read | Read and follow test runs |
runs:report | Publish a test run's results |
builds:read | Read builds and follow their logs |
builds:write | Start and cancel builds, and upload their sources |
artifacts:read | Read artifacts and get their download URLs |
artifacts:write | Reserved: no procedure needs it yet |
signing:use | Read signing credentials' metadata and name one in a signed build |
projects:read | Read the organization's projects |
projects:write | Create, rename and archive projects |
org:read | Read the organization, its members and its invitations |
org:manage | Rename the organization, revoke invitations, set project limits, suspend and remove trusted issuers |
keys:read | List the organization's keys |
keys:write | Revoke keys |
usage:read | Read usage and limits |
Whatever its scopes, a key never makes a change that grants access:
it cannot create keys, invite or change members, register issuers or change signing credentials. A call
without a needed scope is refused with FORBIDDEN, which names every scope the call needs.
Typical sets:
| For | Scopes |
|---|---|
| Driving devices from a script or an agent | devices:read, devices:acquire, devices:drive, apps:install, logs:read |
| Building in CI and installing the result | The five above, and builds:read, builds:write, artifacts:read |
| A dashboard | usage:read, projects:read, builds:read |
Restrict a key to one project
A key for one project sees that project only. Device sessions it starts are in that project, a build or a
session of another project is NOT_FOUND, and organization-level calls are NOT_FOUND too, except the
project list, project lookups by externalRef, usage and limits, which answer for its project alone.
A key for every project must say which project a device call works in, because sessions.create,
sessions.list and runs.list name none. Send the Troupe-Project header:
curl https://api.troupe.ali.ve/v1/sessions/list \
--header "Authorization: Bearer $TROUPE_API_KEY" \
--header "Troupe-Contract: 1.0" \
--header "Troupe-Project: prj_…"Without it the answer is PROJECT_REQUIRED. The header only selects a project the key already reaches. Calls
that name a session, a run or a file need no header: the cloud finds the project from the id. The cloud
procedures for builds and artifacts take projectId in their input instead.
Prefer a key for one project: it limits what a leaked key reaches.
Expiry
A key lasts 90 days unless you ask for fewer, and 90 at most. The one exception is a key whose purpose is
exactly ci, which may last up to 365 days. Asking for more is refused with VALIDATION. An expired or
revoked key is refused with UNAUTHENTICATED.
Use a key
Send the key as a bearer token to https://api.troupe.ali.ve. To see what a key is:
curl https://api.troupe.ali.ve/v1/account/get \
--header "Authorization: Bearer $TROUPE_API_KEY" \
--header "Troupe-Contract: 1.0"{
"principal": "apiKey",
"apiKey": {
"id": "key_…",
"orgId": "org_…",
"projectId": "prj_…",
"scopes": ["devices:read", "devices:acquire", "devices:drive", "apps:install", "logs:read"]
}
}The client takes the key as its token and calls the device procedures exactly as it does against a local runtime:
import { createClient } from '@alivelabs/troupe-client'
const troupe = createClient({
baseUrl: 'https://api.troupe.ali.ve',
token: () => process.env.TROUPE_API_KEY,
})
const { platforms } = await troupe.call('runtime.capabilities', {})An MCP client sends the same key: see MCP in the cloud.
Keep a key on a server or in CI. A browser page gets a stream ticket from your server instead, as
device sessions shows. Twenty failed credentials a minute from one address are
answered with RATE_LIMITED.
Rotate a key
Two keys with the same purpose can be active at once, which is what rotation needs:
- Create a second key with the same purpose and scopes.
- Put the new secret where the old one was.
- Revoke the old key once nothing uses it. The key list's Last used column tells you.
A third active key with that purpose is refused with CONFLICT (too-many-active-keys).
Revoke a key
Revoke on the key list, or keys.revoke, stops a key from its
next request and ends its live device streams. It cannot be undone. A key with keys:write may revoke keys
too. keys.list returns each key's name, purpose, scopes, project,
status, expiry, when it was last used, and the first characters of its secret, never the secret.