---
search:
  tags:
    - Actions
    - POST
seo:
  description: >-
    Answers 404 where the mcp-upstreams feature is off. Adopts tools from the…
    Reference for the POST /api/v2/mcp-upstreams/{id}/adopt endpoint in the
    Anlyon API.
sidebar:
  label: Adopt tools as governed actions
  badge: POST
title: Adopt tools as governed actions
type: openapi-operation
---
Answers 404 where the `mcp-upstreams` feature is off. Adopts tools from the last listing. Each becomes a governed action named `<upstream>-<tool>` whose version pins the tool's definition hash; a tool adopted before whose definition changed is re-adopted as a new version. The suggested class is never applied by itself: approval and impact are what the request chooses (ADR 0020). A tool that cannot be adopted is reported in `skipped` without stopping the others. From an API key, a CLI login or an MCP session this answers 202 with a sign-off link, for a first adoption and a re-adoption alike: only a workspace admin, as a person, can approve it. If the named tools' definitions change before they decide, the request fails as `SIGN_OFF_STALE` and nothing is adopted.

`POST /api/v2/mcp-upstreams/{id}/adopt`

**Request body** (`application/json`, required)

- `tools` (object, required) — Tool name to the operator's choices. 1 to 100 tools.

Request body example:

```json
{
  "tools": {
    "property1": {
      "requiresApproval": true,
      "description": "string",
      "toolHash": "string",
      "impact": {
        "dimension": "string",
        "unit": "string",
        "amount": "string",
        "bound": "exact"
      }
    },
    "property2": {
      "requiresApproval": true,
      "description": "string",
      "toolHash": "string",
      "impact": {
        "dimension": "string",
        "unit": "string",
        "amount": "string",
        "bound": "exact"
      }
    }
  }
}
```

**Responses**

- `200` — Only re-adoptions, or nothing adopted.
- `201` — At least one tool was adopted as a new action.
- `202` — Accepted, not applied: the change loosens a guardrail, so a workspace admin has to approve it. \`Location\` is the request to poll.
- `400` — Bad Request - Invalid input data
- `401` — Unauthorized - Invalid or missing API key
- `403` — Authenticated credential lacks the required scope or workspace access.
- `404` — Not Found - Resource does not exist
- `429` — Too Many Requests - Rate limit exceeded. A workspace's API requests are limited per minute and per UTC clock hour by plan; \`Retry-After\` says when to try again, and an hourly refusal names the limit and when the count resets. Successful responses carry the same limits in \`X-RateLimit-Limit\`, \`X-RateLimit-Remaining\`, \`X-RateLimit-Reset\` (the minute) and \`X-RateLimit-Limit-Hour\`, \`X-RateLimit-Remaining-Hour\`, \`X-RateLimit-Reset-Hour\` (the hour).

Response example, 200:

```json
{
  "success": true,
  "data": {
    "adopted": [
      {
        "tool": "string",
        "action": {
          "id": "act_01JABCDEF",
          "environmentId": "19f5cc2e-7657-437a-9268-83cd3d563563",
          "name": "create-ticket",
          "description": "string",
          "method": "GET",
          "urlTemplate": "string",
          "headers": {},
          "bodyTemplate": {},
          "inputSchema": {},
          "requiresApproval": true,
          "enabled": true,
          "secrets": [
            "string"
          ],
          "currentVersionId": "acv_01JABCDEF",
          "adapter": {
            "type": "stripe.refund",
            "config": {
              "account": "acct_123",
              "mode": "test",
              "currency": "usd"
            },
            "credentialSecret": "STRIPE_TEST_KEY"
          },
          "governed": true,
          "impact": {
            "dimension": "emails",
            "unit": "emails",
            "amount": "count(input.to)",
            "bound": "exact"
          },
          "verify": {
            "url": "https://api.example.com/v1/messages/{{response.id}}",
            "status": 200,
            "match": [
              {
                "path": "status",
                "equals": "sent",
                "absent": true
              }
            ],
            "graphql": {
              "query": "query($id: String!) { issue(id: $id) { title } }",
              "variables": {
                "property1": null,
                "property2": null
              }
            }
          },
          "createdAt": "2019-08-24T14:15:22Z",
          "updatedAt": "2019-08-24T14:15:22Z"
        },
        "readopted": true
      }
    ],
    "skipped": [
      {
        "tool": "string",
        "reason": "string"
      }
    ]
  }
}
```

Response example, 400:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": {
      "url": "Invalid URL format"
    }
  }
}
```

Response example, 401:

```json
{
  "success": false,
  "error": {
    "code": "AUTHENTICATION_ERROR",
    "message": "Invalid or missing API key"
  }
}
```

Response example, 404:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found"
  }
}
```

Response example, 429:

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later."
  }
}
```
