diff --git a/specification/DigitalOcean-public.v2.yaml b/specification/DigitalOcean-public.v2.yaml index f990bba48..ea5fca026 100644 --- a/specification/DigitalOcean-public.v2.yaml +++ b/specification/DigitalOcean-public.v2.yaml @@ -80,6 +80,65 @@ tags: - **Users** and **end user limits** show and limit activity per end user, identified by a session's `actorId`. + To find and call tools in a session, use the + [Action Gateway Data Plane](#tag/Action-Gateway-Data-Plane) endpoints or + connect an MCP client to the session's `mcpUrl`. + + - name: Action Gateway Data Plane + description: |- + The Action Gateway data plane finds and runs tools in a session. These + endpoints are the REST forms of the session's MCP meta tools: + + - `POST /tools/search` is `action_search`. + - `POST /tools/invoke` is `action_invoke`. + - `POST /code/execute` is `action_code`. + + `GET /tools` lists the tools that a session can call, and the + `/approvals` endpoints approve or deny calls waiting for approval. + + **Note:** The data plane uses a separate base URL, + `https://actions.do-ai.run`, the same host as a session's `mcpUrl`. It + is independent of the control-plane API at `https://api.digitalocean.com`. + + ### Authentication and Sessions + + Authenticate with a DigitalOcean personal access token in the + `Authorization: Bearer` header. Custom-scoped tokens need the + `action_gateway:execute` scope. Every request also acts in one session. + Create the session with the + [Create a Session](https://docs.digitalocean.com/reference/api/digitalocean/#tag/Action-Gateway/operation/sessions_create) + endpoint, then send its UUID + in the `X-Session-Id` header or in the request path. The UUID is the value + at the end of the session's `sessionUrn` and `mcpUrl`. + + The token must belong to the user who created the session, in the + session's team. The session's `tools` selection limits which tools are + available, and its `policy` decides whether each call is allowed, needs + approval, or is denied. Policy is enforced the same way on these endpoints + and on the session's MCP URL. Tools that use connections run with the + connections of the session's `actorId`. + + ### Results and Errors + + Successful requests to `/tools/search` and `/code/execute` return a tool + result. Check `status` before reading `output`. `/tools/invoke` returns one + tool result per call, and a well-formed request returns `200` even when + some calls fail. + + Failed requests return the same shape with `status` set to `failed` and + an `error` object. Branch on `error.class`, `error.retriable`, and + `error.recovery_hint` rather than on `error.message`. Request bodies are + limited to 1 MiB. + + ### Approvals + + When a session policy sets `ask` for a call, the call does not run. + Instead, its result fails with `_meta.status` set to `requires_approval`, + an `approval_id`, an `expires_at` time, and a `decide` object describing + the approval request. The session owner sends that request with their own + token. The caller then repeats the call with the same arguments. Over MCP, + clients that support elicitation can approve the call while it waits. + - name: Agent Platform API description: |- The API lets you build GPU-powered AI agents with pre-built or custom foundation models, function and agent routes, and RAG pipelines with knowledge bases. @@ -770,6 +829,7 @@ x-tagGroups: - Actions - Add-Ons - Action Gateway + - Action Gateway Data Plane - Agent Platform API - Apps - Billing @@ -983,6 +1043,30 @@ paths: get: $ref: "resources/action_gateway/users_get.yml" + /tools: + get: + $ref: "resources/action_gateway/gatewayTools_list.yml" + + /tools/search: + post: + $ref: "resources/action_gateway/gatewayTools_list_search.yml" + + /tools/invoke: + post: + $ref: "resources/action_gateway/gatewayTools_run.yml" + + /code/execute: + post: + $ref: "resources/action_gateway/gatewayCode_run.yml" + + /approvals/session/{session_id}/{approval_id}: + post: + $ref: "resources/action_gateway/gatewaySessionApprovals_update.yml" + + /approvals/{approval_id}: + post: + $ref: "resources/action_gateway/gatewayApprovals_update.yml" + /v2/1-clicks: get: $ref: "resources/1-clicks/oneClicks_list.yml" diff --git a/specification/resources/action_gateway/data_plane_responses.yml b/specification/resources/action_gateway/data_plane_responses.yml new file mode 100644 index 000000000..2c9cee8ab --- /dev/null +++ b/specification/resources/action_gateway/data_plane_responses.yml @@ -0,0 +1,202 @@ +bad_request: + description: >- + The request is invalid: the body is not valid JSON, a required field is + missing, or a value is out of range. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: invalid_argument + message: Request is invalid. Fix request arguments and retry. + retriable: false + recovery_hint: fix_args + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + +payment_required: + description: Your team must add prepayment before it can make more tool calls. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: forbidden + message: Prepayment is required before additional tool calls can be made. + retriable: false + recovery_hint: contact_support + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + +unauthorized: + description: >- + The token is missing, invalid, or expired. This response uses the standard + DigitalOcean API error format. + content: + application/json: + schema: + $ref: ../../shared/models/error.yml + example: + id: unauthorized + message: Unable to authenticate you. + +forbidden: + description: >- + The token or session cannot be used for this request. Tokens without the + `action_gateway:execute` scope, and accounts that are not in good + standing, are rejected with the standard DigitalOcean API error format. + Session failures return a tool result: the session ID is missing or + invalid, the session belongs to another user or team, the session has no + policy, or the session policy denies the operation. + content: + application/json: + schema: + oneOf: + - $ref: models/data_plane.yml#/tool_result_error + - $ref: ../../shared/models/error.yml + example: + status: failed + error: + class: forbidden + message: A valid session ID is required to invoke tools. + retriable: false + recovery_hint: fix_args + call_id: '' + +not_found: + description: >- + The approval does not exist or belongs to another session. Approvals for + other callers are reported as not found. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: invalid_argument + message: The requested route is not supported. + retriable: false + recovery_hint: fix_args + call_id: '' + +conflict: + description: >- + The approval was already decided or has expired. Repeat the tool call to + request a new approval. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: invalid_argument + message: The approval is no longer pending. Re-invoke the tool to request a new approval. + retriable: false + recovery_hint: fix_args + call_id: '' + +precondition_failed: + description: >- + Your team is not enabled for Action Gateway, or the session or its + connection is not set up for this call. For example, the session needs an + `actorId` for OAuth tools, an OAuth connection must be reconnected, or the + connection's private network does not match what the MCP server requires. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: forbidden + message: Your team is not enabled for Action Gateway. Contact support to request access. + retriable: false + recovery_hint: contact_support + call_id: '' + +payload_too_large: + description: The request body is larger than 1 MiB. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: invalid_argument + message: Request body is too large. + retriable: false + recovery_hint: fix_args + call_id: '' + +too_many_requests: + description: >- + A rate limit was exceeded. Wait for the number of seconds in `Retry-After`, + when present, before retrying. + headers: + Retry-After: + description: The number of seconds to wait before retrying. + schema: + type: integer + example: 30 + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: rate_limited + message: Rate limit exceeded. Retry after the indicated delay. + retriable: true + recovery_hint: retry_later + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + +server_error: + description: The gateway failed to process the request. Retry later. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: unavailable + message: The server failed to process the request. + retriable: true + recovery_hint: retry_later + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + +service_unavailable: + description: The gateway or a service it depends on is temporarily unavailable. Retry later. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: unavailable + message: Gateway is temporarily unavailable. Retry later. + retriable: true + recovery_hint: retry_later + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + +gateway_timeout: + description: The code did not finish within the execution time limit. + content: + application/json: + schema: + $ref: models/data_plane.yml#/tool_result_error + example: + status: failed + error: + class: timeout + message: Tool execution timed out. + retriable: true + recovery_hint: retry_later + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e diff --git a/specification/resources/action_gateway/examples/curl/gatewayApprovals_update.yml b/specification/resources/action_gateway/examples/curl/gatewayApprovals_update.yml new file mode 100644 index 000000000..d619a02ad --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewayApprovals_update.yml @@ -0,0 +1,8 @@ +lang: cURL +source: |- + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "X-Session-Id: $SESSION_ID" \ + -d '{"decision":"deny"}' \ + "https://actions.do-ai.run/approvals/$APPROVAL_ID" diff --git a/specification/resources/action_gateway/examples/curl/gatewayCode_run.yml b/specification/resources/action_gateway/examples/curl/gatewayCode_run.yml new file mode 100644 index 000000000..60a5182d0 --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewayCode_run.yml @@ -0,0 +1,8 @@ +lang: cURL +source: |- + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "X-Session-Id: $SESSION_ID" \ + -d '{"code":"print(\"hello from Action Gateway\")"}' \ + "https://actions.do-ai.run/code/execute" diff --git a/specification/resources/action_gateway/examples/curl/gatewaySessionApprovals_update.yml b/specification/resources/action_gateway/examples/curl/gatewaySessionApprovals_update.yml new file mode 100644 index 000000000..ec3df9fd5 --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewaySessionApprovals_update.yml @@ -0,0 +1,7 @@ +lang: cURL +source: |- + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -d '{"decision":"approve"}' \ + "https://actions.do-ai.run/approvals/session/$SESSION_ID/$APPROVAL_ID" diff --git a/specification/resources/action_gateway/examples/curl/gatewayTools_list.yml b/specification/resources/action_gateway/examples/curl/gatewayTools_list.yml new file mode 100644 index 000000000..451ddea56 --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewayTools_list.yml @@ -0,0 +1,6 @@ +lang: cURL +source: |- + curl -X GET \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "X-Session-Id: $SESSION_ID" \ + "https://actions.do-ai.run/tools?page_size=20" diff --git a/specification/resources/action_gateway/examples/curl/gatewayTools_list_search.yml b/specification/resources/action_gateway/examples/curl/gatewayTools_list_search.yml new file mode 100644 index 000000000..cdbda71ac --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewayTools_list_search.yml @@ -0,0 +1,8 @@ +lang: cURL +source: |- + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "X-Session-Id: $SESSION_ID" \ + -d '{"queries":[{"use_case":"search the public web for current information"}],"limit":3}' \ + "https://actions.do-ai.run/tools/search" diff --git a/specification/resources/action_gateway/examples/curl/gatewayTools_run.yml b/specification/resources/action_gateway/examples/curl/gatewayTools_run.yml new file mode 100644 index 000000000..4445c1a32 --- /dev/null +++ b/specification/resources/action_gateway/examples/curl/gatewayTools_run.yml @@ -0,0 +1,8 @@ +lang: cURL +source: |- + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "X-Session-Id: $SESSION_ID" \ + -d '{"rationale":"Find current information about DigitalOcean.","tools":[{"tool":"exa_web_search","arguments":{"query":"DigitalOcean cloud computing","max_results":1}}]}' \ + "https://actions.do-ai.run/tools/invoke" diff --git a/specification/resources/action_gateway/gatewayApprovals_update.yml b/specification/resources/action_gateway/gatewayApprovals_update.yml new file mode 100644 index 000000000..601ab8fa3 --- /dev/null +++ b/specification/resources/action_gateway/gatewayApprovals_update.yml @@ -0,0 +1,57 @@ +operationId: gatewayApprovals_update +summary: Decide a Pending Approval by Session Header +description: >- + Approves or denies a tool call that is waiting for approval, identifying the + session with the `X-Session-Id` header. It behaves the same as + `POST /approvals/session/{session_id}/{approval_id}`, which is the path + returned in a result's `_meta.decide`. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id_header +- $ref: parameters.yml#/approval_id +requestBody: + required: true + content: + application/json: + schema: + $ref: models/data_plane.yml#/approval_decision + example: + decision: deny +responses: + '200': + description: The decision was recorded. + content: + application/json: + schema: + $ref: models/data_plane.yml#/approval_decision_response + example: + id: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + status: denied + expires_at: '2026-10-09T18:30:00Z' + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '403': + $ref: data_plane_responses.yml#/forbidden + '404': + $ref: data_plane_responses.yml#/not_found + '409': + $ref: data_plane_responses.yml#/conflict + '412': + $ref: data_plane_responses.yml#/precondition_failed + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable +x-codeSamples: +- $ref: examples/curl/gatewayApprovals_update.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/gatewayCode_run.yml b/specification/resources/action_gateway/gatewayCode_run.yml new file mode 100644 index 000000000..41d32602f --- /dev/null +++ b/specification/resources/action_gateway/gatewayCode_run.yml @@ -0,0 +1,73 @@ +operationId: gatewayCode_run +summary: Execute Code +description: >- + Runs Python code in an ephemeral sandbox and returns its `stdout`, `stderr`, + and `exit_code`. This is the REST form of the `action_code` MCP tool. Use it + for computation, parsing, and data processing, or to chain several catalog + tools in one request. + + + The sandbox provides `invoke_digitalocean_tool(name, arguments)`, which calls + a catalog tool and returns a tool result. Those calls follow the session's + tool selection and policy. Each request runs in a fresh sandbox, so state is + not kept between requests. Execution, including tool calls, is time limited. + Code that runs too long fails with a `timeout` error. + + + A nonzero exit code is still a `succeeded` result; check `output.exit_code`. + If the session policy requires approval for `action_code`, the response is a + failed result with `_meta.status` set to `requires_approval`. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id_header +requestBody: + required: true + content: + application/json: + schema: + $ref: models/data_plane.yml#/execute_code_request + example: + code: print("hello from Action Gateway") +responses: + '200': + description: >- + The code ran, or it is waiting for approval. Check `status` before + reading `output`. + content: + application/json: + schema: + $ref: models/data_plane.yml#/execute_code_response + example: + status: succeeded + output: + stdout: | + hello from Action Gateway + exit_code: 0 + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '403': + $ref: data_plane_responses.yml#/forbidden + '412': + $ref: data_plane_responses.yml#/precondition_failed + '413': + $ref: data_plane_responses.yml#/payload_too_large + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable + '504': + $ref: data_plane_responses.yml#/gateway_timeout +x-codeSamples: +- $ref: examples/curl/gatewayCode_run.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/gatewaySessionApprovals_update.yml b/specification/resources/action_gateway/gatewaySessionApprovals_update.yml new file mode 100644 index 000000000..bbd387d69 --- /dev/null +++ b/specification/resources/action_gateway/gatewaySessionApprovals_update.yml @@ -0,0 +1,64 @@ +operationId: gatewaySessionApprovals_update +summary: Decide a Pending Approval +description: >- + Approves or denies a tool call that is waiting for approval. A tool result + returns `_meta.status` set to `requires_approval` when the session policy + sets `ask` for the call, and its `_meta.decide` gives this request's path. + + + Send the decision with the session owner's token. Approval is a separate + control request, not a tool, so an agent cannot approve its own calls. After + approving, repeat the original call with the same arguments before + `expires_at`. An approval applies only to that session, tool version, and + set of arguments, and is used once. To ask again after a denial or expiry, + repeat the tool call. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id +- $ref: parameters.yml#/approval_id +requestBody: + required: true + content: + application/json: + schema: + $ref: models/data_plane.yml#/approval_decision + example: + decision: approve +responses: + '200': + description: The decision was recorded. + content: + application/json: + schema: + $ref: models/data_plane.yml#/approval_decision_response + example: + id: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + status: approved + expires_at: '2026-10-09T18:30:00Z' + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '403': + $ref: data_plane_responses.yml#/forbidden + '404': + $ref: data_plane_responses.yml#/not_found + '409': + $ref: data_plane_responses.yml#/conflict + '412': + $ref: data_plane_responses.yml#/precondition_failed + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable +x-codeSamples: +- $ref: examples/curl/gatewaySessionApprovals_update.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/gatewayTools_list.yml b/specification/resources/action_gateway/gatewayTools_list.yml new file mode 100644 index 000000000..0adc9418a --- /dev/null +++ b/specification/resources/action_gateway/gatewayTools_list.yml @@ -0,0 +1,81 @@ +operationId: gatewayTools_list +summary: List Session Tools +description: >- + Lists the catalog tools that a session can call, ordered by name. The list + applies the session's `tools` selection and omits tools that its policy + always denies. + + + Each page includes an `ETag` header. Send it in `If-None-Match` to receive + `304 Not Modified` while the page is unchanged. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id_header +- $ref: parameters.yml#/session_tools_page_size +- $ref: parameters.yml#/session_tools_page_token +- $ref: parameters.yml#/if_none_match +responses: + '200': + description: A page of tools. + headers: + ETag: + description: An identifier for this page's contents. + schema: + type: string + example: '"3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c"' + content: + application/json: + schema: + $ref: models/data_plane.yml#/list_session_tools_response + example: + version: v1 + tools: + - name: exa_web_search + title: Web Search + description: Search the public web and return ranked results with titles and URLs. + inputSchema: + type: object + required: + - query + properties: + query: + type: string + max_results: + type: integer + annotations: + title: Web Search + readOnlyHint: true + openWorldHint: true + _meta: + version: '1' + parallelizable: true + streaming_safe: false + version: '1' + parallelizable: true + streamingSafe: false + next_page_token: eyJ2IjoxLCJhZnRlciI6ImV4YV93ZWJfc2VhcmNoIiwiZnAiOiI5YzFlNWI3YTNkMmY0ZTYwIn0 + '304': + description: The page is unchanged since the `ETag` in `If-None-Match`. + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '403': + $ref: data_plane_responses.yml#/forbidden + '412': + $ref: data_plane_responses.yml#/precondition_failed + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable +x-codeSamples: +- $ref: examples/curl/gatewayTools_list.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/gatewayTools_list_search.yml b/specification/resources/action_gateway/gatewayTools_list_search.yml new file mode 100644 index 000000000..0a9209cbf --- /dev/null +++ b/specification/resources/action_gateway/gatewayTools_list_search.yml @@ -0,0 +1,82 @@ +operationId: gatewayTools_list_search +summary: Search Session Tools +description: >- + Finds the catalog tools that fit one or more use cases. This is the REST form + of the `action_search` MCP tool. Each query is searched independently and + returns its own result group, so you can see which tools serve which goal. + Results include each tool's `inputSchema`, so you can call a tool with + `POST /tools/invoke` without another lookup. + + + Results include only tools that the session can call. If the session policy + requires approval for `action_search`, the response is a failed result with + `_meta.status` set to `requires_approval`. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id_header +requestBody: + required: true + content: + application/json: + schema: + $ref: models/data_plane.yml#/search_tools_request + example: + queries: + - use_case: search the public web for current information + limit: 3 +responses: + '200': + description: >- + The search ran, or it is waiting for approval. Check `status` before + reading `output`. + content: + application/json: + schema: + $ref: models/data_plane.yml#/search_tools_response + example: + status: succeeded + output: + results: + - index: 1 + use_case: search the public web for current information + results: + - name: exa_web_search + version: '1' + title: Web Search + description: Search the public web and return ranked results with titles and URLs. + inputSchema: + type: object + required: + - query + properties: + query: + type: string + max_results: + type: integer + score: 0.92 + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '403': + $ref: data_plane_responses.yml#/forbidden + '412': + $ref: data_plane_responses.yml#/precondition_failed + '413': + $ref: data_plane_responses.yml#/payload_too_large + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable +x-codeSamples: +- $ref: examples/curl/gatewayTools_list_search.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/gatewayTools_run.yml b/specification/resources/action_gateway/gatewayTools_run.yml new file mode 100644 index 000000000..27f1ad3be --- /dev/null +++ b/specification/resources/action_gateway/gatewayTools_run.yml @@ -0,0 +1,120 @@ +operationId: gatewayTools_run +summary: Invoke Session Tools +description: >- + Runs 1 to 10 tool calls in parallel and returns one result per call, in + request order. This is the REST form of the `action_invoke` MCP tool. + + + Each call is checked against the session's tool selection and policy, uses + the session actor's connections, and applies any output view bound to the + tool in the session. A well-formed request returns `200` even when some calls + fail. Check each item's `result.status`, and use `success_count` and + `error_count` to branch without scanning every item. + + + A call whose policy requires approval fails with `_meta.status` set to + `requires_approval`. Have the session's owner approve it, then repeat the + call with the same arguments. A call that needs the end user to connect a + provider fails with `_meta.status` set to `requires_authorization` or + `requires_connection_configuration`. +tags: +- Action Gateway Data Plane +servers: +- url: https://actions.do-ai.run + description: production +parameters: +- $ref: parameters.yml#/session_id_header +requestBody: + required: true + content: + application/json: + schema: + $ref: models/data_plane.yml#/invoke_tools_request + example: + rationale: Find current information about DigitalOcean. + tools: + - tool: exa_web_search + arguments: + query: DigitalOcean cloud computing + max_results: 1 +responses: + '200': + description: >- + The calls ran. Each item reports its own success or failure. + content: + application/json: + schema: + $ref: models/data_plane.yml#/invoke_tools_response + examples: + succeeded: + summary: A successful call + value: + total_count: 1 + success_count: 1 + error_count: 0 + results: + - index: 0 + tool: exa_web_search + version: '1' + result: + status: succeeded + output: + provider: exa + results: + - title: DigitalOcean | Cloud Infrastructure for Developers + url: https://www.digitalocean.com/ + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + requires_approval: + summary: A call that needs approval + value: + total_count: 1 + success_count: 0 + error_count: 1 + results: + - index: 0 + tool: exa_web_search + version: '1' + result: + status: failed + error: + class: execution_failed + message: >- + Tool "exa_web_search" requires user approval before it can run. + Ask the user to approve approval_id 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + (expires 2026-10-09T18:30:00Z) using their own credentials, then call + the tool again with the same arguments. + retriable: true + recovery_hint: retry_later + call_id: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + _meta: + status: requires_approval + approval_id: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + tool_name: exa_web_search + expires_at: '2026-10-09T18:30:00Z' + decide: + method: POST + path: /approvals/session/2b3c9f0e-1a4d-4e5f-8a7b-6c5d4e3f2a1b/7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + body: '{"decision": "approve"}' + '400': + $ref: data_plane_responses.yml#/bad_request + '401': + $ref: data_plane_responses.yml#/unauthorized + '402': + $ref: data_plane_responses.yml#/payment_required + '403': + $ref: data_plane_responses.yml#/forbidden + '412': + $ref: data_plane_responses.yml#/precondition_failed + '413': + $ref: data_plane_responses.yml#/payload_too_large + '429': + $ref: data_plane_responses.yml#/too_many_requests + '500': + $ref: data_plane_responses.yml#/server_error + '503': + $ref: data_plane_responses.yml#/service_unavailable +x-codeSamples: +- $ref: examples/curl/gatewayTools_run.yml +security: +- bearer_auth: + - action_gateway:execute diff --git a/specification/resources/action_gateway/models/data_plane.yml b/specification/resources/action_gateway/models/data_plane.yml new file mode 100644 index 000000000..99f1aa0ee --- /dev/null +++ b/specification/resources/action_gateway/models/data_plane.yml @@ -0,0 +1,475 @@ +error_envelope: + type: object + description: Describes why a request or tool call failed and whether retrying can help. + properties: + class: + type: string + description: >- + The failure class. Branch on this value rather than on `message`, which + may change. + enum: + - invalid_argument + - unauthorized + - forbidden + - rate_limited + - not_found + - timeout + - upstream_error + - output_too_large + - execution_failed + - unavailable + - canceled + - render_required + - unsupported_media_type + - view_materialization_failed + example: invalid_argument + message: + type: string + description: A human-readable explanation that is safe to show to an agent or end user. + example: Request is invalid. Fix request arguments and retry. + retriable: + type: boolean + description: Whether retrying the same request later can succeed. + example: false + recovery_hint: + type: string + description: The suggested next step. + enum: + - fix_args + - refresh_auth + - retry_later + - narrow_output + - contact_support + - verify_provider_state + example: fix_args + +tool_result: + type: object + description: >- + The result of one tool call. Check `status` before reading `output`. A call + that needs approval, authorization, or connection setup fails with a + `_meta.status` that names the step to complete. + properties: + status: + type: string + enum: + - succeeded + - failed + example: succeeded + output: + type: object + description: >- + The tool's JSON output. Present when `status` is `succeeded`. Its shape + is the tool's output schema, or the output view bound to the tool in + the session. + additionalProperties: true + example: + provider: exa + results: + - title: DigitalOcean + url: https://www.digitalocean.com/ + error: + $ref: '#/error_envelope' + call_id: + type: string + description: A unique identifier for this call. Include it when contacting support. + example: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + _meta: + type: object + description: >- + Additional result metadata. Keys include: + + + - `status`: `requires_approval`, `requires_authorization`, or + `requires_connection_configuration` when the call stopped before + running. + + - `approval_id`, `tool_name`, `expires_at`, and `decide`: present with + `requires_approval`. `decide` gives the `method`, `path`, and `body` + of the approval request to send. Approve the call, then repeat it + with the same arguments before `expires_at`. + + - `retry`: `{"attempt_count": N, "exhausted": bool}` when the gateway + retried the call. + + - `output_view`: the output view applied to the result. + additionalProperties: true + example: + status: requires_approval + approval_id: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + tool_name: exa_web_search + expires_at: '2026-10-09T18:30:00Z' + decide: + method: POST + path: /approvals/session/2b3c9f0e-1a4d-4e5f-8a7b-6c5d4e3f2a1b/7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + body: '{"decision": "approve"}' + required: + - status + - call_id + +tool_result_error: + type: object + description: A failed request. The body has the same shape as a failed tool result. + properties: + status: + type: string + enum: + - failed + example: failed + error: + $ref: '#/error_envelope' + call_id: + type: string + description: A unique identifier for this request, when one was assigned. + example: call_8b7c6d5e-4f3a-4b2c-9d1e-0f9a8b7c6d5e + required: + - status + - error + - call_id + +session_tool: + type: object + description: >- + A catalog tool the session can call, in MCP tool format plus Action Gateway + fields. + properties: + name: + type: string + description: The provider-qualified tool name. Pass it as `tool` to invoke the tool. + example: exa_web_search + title: + type: string + example: Web Search + description: + type: string + example: Search the public web and return ranked results with titles and URLs. + inputSchema: + type: object + description: The JSON Schema for the tool's `arguments`. + additionalProperties: true + example: + type: object + required: + - query + properties: + query: + type: string + max_results: + type: integer + outputSchema: + type: object + description: >- + The JSON Schema for the tool's output, or for the output view bound to + the tool in the session. + additionalProperties: true + example: + type: object + annotations: + $ref: 'tools.yml#/tool_annotations' + _meta: + type: object + description: Additional tool metadata. + additionalProperties: true + example: + version: '1' + parallelizable: true + streaming_safe: false + version: + type: string + description: The immutable tool version. + example: '1' + parallelizable: + type: boolean + description: Whether the tool can run alongside other calls in one invoke request. + example: true + streamingSafe: + type: boolean + example: false + +list_session_tools_response: + type: object + properties: + version: + type: string + description: The catalog version. + example: v1 + tools: + type: array + description: Tools ordered by name. + items: + $ref: '#/session_tool' + next_page_token: + $ref: 'shared.yml#/next_page_token' + +search_query: + type: object + properties: + use_case: + type: string + description: >- + A normalized English description of one complete use case: the action + and the intended outcome, including any app the user named. Put + personal identifiers in `known_fields` instead. Up to 512 bytes and 32 + search terms, with at least one letter or digit. + example: search the public web for current information + known_fields: + type: string + description: >- + Known workflow inputs as one string of comma-separated `key:value` + pairs, such as identifiers or settings. Used as context, not for + keyword matching. + example: 'project:checkout' + required: + - use_case + +search_tools_request: + type: object + properties: + queries: + type: array + description: >- + Independent search queries. Split separate actions into separate + queries. Each query is searched on its own and gets its own result + group. + minItems: 1 + maxItems: 5 + items: + $ref: '#/search_query' + providers: + type: array + description: Restricts every query to these providers. + items: + type: string + example: + - exa + tags: + type: array + description: Restricts every query to tools with these tags. + items: + type: string + example: + - web-search + limit: + type: integer + description: >- + The maximum number of tools returned per query. Values above 50 are + capped at 50. + minimum: 1 + default: 10 + example: 3 + required: + - queries + +search_tools_result: + type: object + description: A tool that matched a query. + properties: + name: + type: string + description: The provider-qualified tool name. Pass it as `tool` to invoke the tool. + example: exa_web_search + version: + type: string + example: '1' + title: + type: string + example: Web Search + description: + type: string + example: Search the public web and return ranked results with titles and URLs. + inputSchema: + type: object + description: The JSON Schema for the tool's `arguments`. + additionalProperties: true + example: + type: object + required: + - query + properties: + query: + type: string + score: + type: number + description: The relevance score. Higher is more relevant. + example: 0.92 + +search_tools_group: + type: object + description: The results for one query, in request order. + properties: + index: + type: integer + description: The 1-based position of the query in the request. + example: 1 + use_case: + type: string + description: The query's `use_case`. + example: search the public web for current information + results: + type: array + items: + $ref: '#/search_tools_result' + guidance: + type: string + description: A suggested next step. Present only when no tools matched. + example: No catalog tools matched this query. Retry with different terms. + +search_tools_response: + allOf: + - $ref: '#/tool_result' + - type: object + properties: + output: + type: object + properties: + results: + type: array + description: One result group per query, in request order. + items: + $ref: '#/search_tools_group' + +tool_call: + type: object + properties: + tool: + type: string + description: >- + The tool name, as returned by `GET /tools` or `POST /tools/search`. + Names are case-insensitive. + example: exa_web_search + version: + type: string + description: An immutable tool version. Omit to use the released version. + example: '1' + arguments: + type: object + description: The tool's arguments, matching its `inputSchema`. + additionalProperties: true + example: + query: DigitalOcean cloud computing + max_results: 1 + required: + - tool + +invoke_tools_request: + type: object + properties: + rationale: + type: string + description: >- + One sentence explaining why the tools are being called. Values longer + than 512 characters are truncated. + example: Find current information about DigitalOcean. + tools: + type: array + description: The tool calls to run in parallel. + minItems: 1 + maxItems: 10 + items: + $ref: '#/tool_call' + required: + - tools + +invoke_tools_item: + type: object + properties: + index: + type: integer + description: The 0-based position of the call in the request. + example: 0 + tool: + type: string + description: The canonical tool name. + example: exa_web_search + version: + type: string + description: The tool version that ran. + example: '1' + result: + $ref: '#/tool_result' + +invoke_tools_response: + type: object + properties: + total_count: + type: integer + example: 1 + success_count: + type: integer + example: 1 + error_count: + type: integer + example: 0 + results: + type: array + description: One result per call, in request order. + items: + $ref: '#/invoke_tools_item' + +execute_code_request: + type: object + properties: + code: + type: string + description: >- + Python source to run. The sandbox provides + `invoke_digitalocean_tool(name: str, arguments: dict) -> dict`, which + calls a catalog tool under the session's policy and returns a tool + result. + example: print("hello from Action Gateway") + required: + - code + +execute_code_response: + allOf: + - $ref: '#/tool_result' + - type: object + properties: + output: + type: object + properties: + stdout: + type: string + example: | + hello from Action Gateway + stderr: + type: string + description: Omitted when empty. + example: '' + exit_code: + type: integer + format: int32 + description: The process exit code. A nonzero exit code is still a succeeded result. + example: 0 + +approval_decision: + type: object + properties: + decision: + type: string + description: Whether to let the pending call run. + enum: + - approve + - deny + example: approve + required: + - decision + +approval_decision_response: + type: object + properties: + id: + type: string + description: The approval ID. + example: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + status: + type: string + enum: + - approved + - denied + example: approved + expires_at: + type: string + format: date-time + description: Repeat an approved call with the same arguments before this time. + example: '2026-10-09T18:30:00Z' diff --git a/specification/resources/action_gateway/parameters.yml b/specification/resources/action_gateway/parameters.yml index 37558bfb2..e331fc71d 100644 --- a/specification/resources/action_gateway/parameters.yml +++ b/specification/resources/action_gateway/parameters.yml @@ -559,3 +559,73 @@ sort_direction_users: - asc - desc example: desc + +session_id_header: + name: X-Session-Id + in: header + required: true + description: >- + The session to act in: the UUID at the end of the session's `sessionUrn` + and `mcpUrl`. The session must belong to the authenticated user and team. + schema: + type: string + format: uuid + example: 2b3c9f0e-1a4d-4e5f-8a7b-6c5d4e3f2a1b + +session_id: + name: session_id + in: path + required: true + description: >- + The session UUID: the value at the end of the session's `sessionUrn` and + `mcpUrl`. The session must belong to the authenticated user and team. + schema: + type: string + format: uuid + example: 2b3c9f0e-1a4d-4e5f-8a7b-6c5d4e3f2a1b + +approval_id: + name: approval_id + in: path + required: true + description: The `approval_id` from a tool result whose `_meta.status` is `requires_approval`. + schema: + type: string + format: uuid + example: 7d3c2b1a-9e8f-4a6b-8c5d-4e3f2a1b0c9d + +session_tools_page_size: + name: page_size + in: query + required: false + description: >- + The number of tools returned per page, from 1 to 100. Defaults to 20 when + omitted. Other values are rejected with 400. + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + example: 20 + +session_tools_page_token: + name: page_token + in: query + required: false + description: >- + The `next_page_token` from the previous response. A token is only valid + for the session that received it. + schema: + type: string + example: eyJ2IjoxLCJhZnRlciI6ImV4YV93ZWJfc2VhcmNoIiwiZnAiOiI5YzFlNWI3YTNkMmY0ZTYwIn0 + +if_none_match: + name: If-None-Match + in: header + required: false + description: >- + The `ETag` from a previous response for the same page. If the page is + unchanged, the response is `304 Not Modified` with no body. + schema: + type: string + example: '"3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c"' diff --git a/spectral/ruleset.yml b/spectral/ruleset.yml index 66c8ef3c6..863073f1a 100644 --- a/spectral/ruleset.yml +++ b/spectral/ruleset.yml @@ -6,11 +6,12 @@ functions: - ensureSchemaHasType rules: + # Edge does not rate limit the Action Gateway data plane (actions.do-ai.run), so it sends no ratelimit-x headers. ratelimit-headers: description: Response must include ratelimit-x headers message: '{{description}}; missing {{property}}' severity: error - given: $..responses.* + given: "$.paths[?(!@property.match(/^\\/(tools|code|approvals)(\\/|$)/))]..responses.*" then: - field: headers.ratelimit-limit function: truthy @@ -57,6 +58,7 @@ rules: field: $ref function: truthy + # Action Gateway data plane paths (actions.do-ai.run) are unversioned. path-must-include-version: description: Path must include the version message: '{{description}}; {{property}} incorrect' @@ -66,7 +68,7 @@ rules: then: function: pattern functionOptions: - match: '^(/v[12]/.*|/api/v[12]/.*)$' + match: '^(/v[12]/.*|/api/v[12]/.*|/tools(/search|/invoke)?|/code/execute|/approvals/.+)$' endpoint-ref-must-be-file: description: Endpoint must a $ref to a file in resources/