A valid request URL is required to generate request examples{
"tool": "<string>",
"isError": true,
"output": {
"summary": "<string>",
"text": "<string>",
"frameId": "<string>"
},
"files": [
{
"mediaType": "<string>",
"url": "<string>"
}
],
"threadId": "<string>",
"messageId": "<string>"
}{
"approval": {
"tool": "<string>",
"arguments": {},
"prompt": "<string>"
},
"approvalId": "<string>",
"toolCallId": "<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": "not_found",
"message": "<string>"
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "service_unavailable",
"message": "<string>"
}
}Run one tool the person named
Run ONE tool directly, as yourself, with no model involved and no tokens spent. This is what a /screenshot or a /press_key home typed in the console’s composer becomes.
It reaches the SAME tool surface a copilot turn drives, under the same permissions and the same approval table, so a command cannot do what the conversation would refuse. Two things differ from a turn, and both follow from the person having typed the tool themselves. It works on a deployment with NO copilot configured, because pressing Home should not need a model. And the receipt is not labelled via copilot: you did this, not the copilot on your behalf.
WHAT IS DIRECT. Every read, plus open_app, press_key, release_device, list_runs, cancel_run and resume_run: the tools whose arguments a person can write out in full. tap, swipe, type_text and long_press are NOT, because the useful form of “tap the login button” is a sentence and the coordinates are the copilot’s job; nor is start_run, which takes a goal and a budget. Those answer 400 and say to ask in the conversation instead. A tool your session has no scope for is a different answer: 403, naming the scope, because the remedy is an administrator rather than a rephrasing.
WHICH PHONE. context and mentions work exactly as they do on POST /v1/copilot, so a command typed on a device’s page needs no deviceId. A tool that needs one, with nothing in the input and nothing the context could resolve, is 400.
IN A CONVERSATION. Send threadId and the result is appended to that thread as a tool part, which is what makes a command followed by a sentence work: the next turn’s copilot sees what the command did instead of a gap. Without it the command still runs and leaves no trace in any conversation.
IF IT ASKS FIRST. install_app and start_run raise a card in a command exactly as they do in the chat: the answer is 202 with an approval descriptor and an approvalId, and nothing has run. Show the card, then send the SAME tool and input again with that approvalId. The question is stored on the thread, so it can be answered minutes later and on any machine, and a gated command therefore needs a threadId to put its card in. The tool and arguments are checked against what was asked: a yes to one command cannot be spent on another.
Needs a signed in person, as every copilot route does.
Hosted deployments only. A local checkout does not mount this route, so calling it there is a 404.
A valid request URL is required to generate request examples{
"tool": "<string>",
"isError": true,
"output": {
"summary": "<string>",
"text": "<string>",
"frameId": "<string>"
},
"files": [
{
"mediaType": "<string>",
"url": "<string>"
}
],
"threadId": "<string>",
"messageId": "<string>"
}{
"approval": {
"tool": "<string>",
"arguments": {},
"prompt": "<string>"
},
"approvalId": "<string>",
"toolCallId": "<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": "not_found",
"message": "<string>"
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "service_unavailable",
"message": "<string>"
}
}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 command.
The tool name, as the MCP reference spells it.
The tool's arguments, in its own published input schema.
What the console knows and the sentence does not say. Every device id here must be one your session can address, or the request is 400 naming the field. Resolution order for which phone a turn is about: pinned, then mentions, then context.device, then the device this thread last acted on, then the only device you have, then a data-device-choice card. THIS BLOCK IS NOT REPLAYED: it describes where somebody is now, so it is read from this request only and never from the stored thread.
Show child attributes
Show child attributes
Device ids the person named with an @ in this message. The id travels, never the typed name, so renaming a phone cannot re-point a message. Exactly one names the turn's device; two or more raise the chooser.
10Append the result to this thread, as a tool part. Required for a command that asks first.
^[A-Za-z0-9_-]{1,128}$The id from a 202 this route returned, sent with the same tool and input to run it.
Response
The tool ran. isError is the tool's own verdict, not an HTTP status: a refusal by the device is a 200 with isError true.
The tool that ran.
The tool's own verdict.
Show child attributes
Show child attributes
Pictures the tool returned, as data: URLs, when small enough to carry inline.
Show child attributes
Show child attributes
The thread the result was appended to, when one was named.
The assistant message the result was written as.