A valid request URL is required to generate request examples{
"runId": "<string>",
"agentRunId": "<string>",
"state": "queued",
"maxSteps": 123,
"deadlineAt": "<string>",
"executor": "phonebase",
"vrxBaseUrl": "<string>"
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "unauthorized",
"reason": "missing_credentials",
"hint": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}Start a run on a device
Hand one device your organisation holds to a run with a goal and a budget. Your organisation must already hold it: starting a run is permission to let a loop act for you, not permission to take the device, and this route is the one place that difference is visible (a gesture DOES take).
executor says who drives it. phonebase runs it for you and returns no url. self_hosted means your own worker drives it, and the response then carries the run scoped device surface url exactly once: treat it as a credential and give it to one worker only. Left out, it is self_hosted for an API key and phonebase for a signed-in person. Your credential needs both runs:start and devices:act.
A valid request URL is required to generate request examples{
"runId": "<string>",
"agentRunId": "<string>",
"state": "queued",
"maxSteps": 123,
"deadlineAt": "<string>",
"executor": "phonebase",
"vrxBaseUrl": "<string>"
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "unauthorized",
"reason": "missing_credentials",
"hint": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}Authorizations
The control surface credential. Send Authorization: Bearer <token>.
Two kinds of token are accepted and they are told apart by shape, not by a separate header. A token beginning pbk_ is an org scoped API key, whose public half and secret half are generated together and of which only a hash of the secret is ever stored; anything else is treated as an OAuth 2.1 access token and verified against the authorization server's keys.
Both resolve to the same context: an org, a principal and a set of scopes. Nothing downstream branches on which channel you used, with one deliberate exception, key management, which requires a signed-in person so that a key can never mint another key.
Scopes are enforced when MCP tools are REGISTERED rather than when they are called, so a tool your credential cannot use is absent from tools/list rather than refused mid gesture.
Body
The device, the goal, and optional budget ceilings.
The device to drive.
What the run should accomplish, in plain language.
Optional, and not compared: the run starts under the lease your ORGANISATION holds on that device, whoever inside it took that lease. Your organisation holding none is 409 lease_required; unlike a gesture, this route never takes one for you.
Ceiling on metered steps. A value above the deployment's own limit is capped to that limit, not refused: the limit is deployment configuration rather than a published number, so it cannot be asked for exactly. Read the run's maxSteps for the effective ceiling. A value of the wrong type is refused.
x >= 1Wall clock budget in milliseconds, at least 1000 (a smaller window would be born expired). Capped by the deployment's limit and by the lease's own expiry, whichever comes first.
x >= 1000Who drives the run. Optional: left out, it is self_hosted for an API key and phonebase for a signed-in person. Any other value is refused.
phonebase, self_hosted Response
The run started.
The run. Use it on every later call about this run.
The run's shareable id: what every receipt the run produces carries, and what a permalink is built on. GET /v1/runs/{runId} accepts it in place of the runId.
The run's state at the moment it was created.
queued, running, needs_user_control, paused, succeeded, failed, canceled The EFFECTIVE step ceiling: your maxSteps, capped by the deployment's limit. Read this rather than assuming your ask was honoured; a request above the limit is accepted and capped, not refused.
The effective wall-clock deadline, ISO 8601: your deadlineMs, capped by the deployment's limit and by the lease's own expiry, whichever comes first.
Who drives the run, as decided: what you asked for, or the default for your credential. phonebase runs it for you; self_hosted means your own worker drives it.
phonebase, self_hosted The run scoped device surface base url, present ONLY when executor is self_hosted. It CARRIES THE RUN TOKEN, so it is a credential: hand it to exactly one worker and to nothing else. It is returned here and never again.