# `PUT /v1/domain/services/{serviceId}` — Connect or replace a domain service

Part of the [Bkper Managed Agent API](https://bkper.com/docs/api/managed-agent.md). Read its guide for authentication, conventions, and examples.

The services of the caller's domain (Bkper's verified domain of the account), used by every member's agent after their own services for the same host. Through an App or channel bot, 404. Only the domain's admin: 403 domain_admin_required for anyone else, members included. As PUT /v1/services/{serviceId}, within the domain: 409 host_in_use when another of the domain's services holds a host, 409 service_limit at 20 services. A domain's secret works for every member and any App they authorize.

## Operation contract

```json
{
  "operationId": "putDomainService",
  "tags": [
    "Services"
  ],
  "summary": "Connect or replace a domain service",
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "parameters": [
    {
      "name": "serviceId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
      }
    }
  ],
  "description": "The services of the caller's domain (Bkper's verified domain of the account), used by every member's agent after their own services for the same host. Through an App or channel bot, 404. Only the domain's admin: 403 domain_admin_required for anyone else, members included. As PUT /v1/services/{serviceId}, within the domain: 409 host_in_use when another of the domain's services holds a host, 409 service_limit at 20 services. A domain's secret works for every member and any App they authorize.",
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/PutService"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Replaced",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Service"
          }
        }
      }
    },
    "201": {
      "description": "Connected",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Service"
          }
        }
      }
    },
    "400": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "410": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "411": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## Schemas

### PutService

```json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "pattern": "^[^\\u0000-\\u001f\\u007f]*$",
      "description": "Display name, such as GitHub."
    },
    "targets": {
      "minItems": 1,
      "maxItems": 10,
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "host": {
                "type": "string",
                "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$",
                "description": "An exact lowercase DNS name with at least two labels, reached over HTTPS on its default port: no wildcard, port, path, IP literal or localhost. Bkper and Cloudflare Artifacts hosts are refused (400 host_not_allowed): they have their own routes."
              },
              "scheme": {
                "type": "string",
                "const": "bearer"
              }
            },
            "required": [
              "host",
              "scheme"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "host": {
                "type": "string",
                "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$",
                "description": "An exact lowercase DNS name with at least two labels, reached over HTTPS on its default port: no wildcard, port, path, IP literal or localhost. Bkper and Cloudflare Artifacts hosts are refused (400 host_not_allowed): they have their own routes."
              },
              "scheme": {
                "type": "string",
                "const": "basic"
              },
              "username": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[^\\x00-\\x1f\\x7f:]+$",
                "description": "basic only: the username sent with the secret. No colon."
              }
            },
            "required": [
              "host",
              "scheme",
              "username"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "host": {
                "type": "string",
                "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$",
                "description": "An exact lowercase DNS name with at least two labels, reached over HTTPS on its default port: no wildcard, port, path, IP literal or localhost. Bkper and Cloudflare Artifacts hosts are refused (400 host_not_allowed): they have their own routes."
              },
              "scheme": {
                "type": "string",
                "const": "header"
              },
              "header": {
                "type": "string",
                "pattern": "^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$",
                "description": "header only: the header that carries the secret, such as x-api-key. Not host, content-length, transfer-encoding, connection."
              }
            },
            "required": [
              "host",
              "scheme",
              "header"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "host": {
                "type": "string",
                "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$",
                "description": "google only: a Google API host, ending in .googleapis.com, such as analyticsdata.googleapis.com. Not oauth2.googleapis.com (400 host_not_allowed)."
              },
              "scheme": {
                "type": "string",
                "const": "google"
              }
            },
            "required": [
              "host",
              "scheme"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A host that gets the credential, and how: `bearer` sends `Authorization: Bearer <secret>`; `basic` sends `Authorization: Basic base64(<username>:<secret>)`; `header` sends `<header>: <secret>`; `google` sends `Authorization: Bearer <access token>`, made from the stored Google service account key for the service's scopes."
      },
      "description": "1 to 10 hosts, each once. A service with a `google` target has only `google` targets."
    },
    "google": {
      "type": "object",
      "properties": {
        "scopes": {
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^https:\\/\\/www\\.googleapis\\.com\\/auth\\/[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)*$"
          },
          "description": "1 to 20 OAuth scopes, each https://www.googleapis.com/auth/…, such as https://www.googleapis.com/auth/analytics.readonly."
        }
      },
      "required": [
        "scopes"
      ],
      "additionalProperties": false,
      "description": "Google services only, and always with `google` targets: the scopes their access tokens are made for."
    },
    "secret": {
      "type": "string",
      "minLength": 1,
      "maxLength": 8192,
      "description": "Up to 8192 characters. A token: printable ASCII, without spaces at either end. For `google`, the service account's JSON key file as it is (400 invalid_request when it is not a service account key). Write-only: never returned, logged or shown to the agent."
    }
  },
  "required": [
    "name",
    "targets",
    "secret"
  ],
  "additionalProperties": false
}
```

### Service

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "targets": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string"
          },
          "scheme": {
            "type": "string",
            "description": "How the secret is sent. Open: bearer | basic | header | google."
          },
          "username": {
            "description": "basic only.",
            "type": "string"
          },
          "header": {
            "description": "header only.",
            "type": "string"
          }
        },
        "required": [
          "host",
          "scheme"
        ]
      }
    },
    "google": {
      "description": "Google services only.",
      "type": "object",
      "properties": {
        "scopes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "clientEmail": {
          "type": "string",
          "description": "The service account's email, read from its key: share what the agent should reach with it."
        }
      },
      "required": [
        "scopes",
        "clientEmail"
      ]
    },
    "createdAt": {
      "type": "string",
      "pattern": "^\\d+$",
      "description": "Milliseconds since the epoch, as a string."
    },
    "updatedAt": {
      "type": "string",
      "pattern": "^\\d+$",
      "description": "Milliseconds since the epoch, as a string."
    }
  },
  "required": [
    "id",
    "name",
    "targets",
    "createdAt",
    "updatedAt"
  ],
  "description": "A service the user connected: requests the agent sends to its hosts carry the stored secret, added outside the sandbox. The secret is never returned."
}
```

### Error

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "Stable code, e.g. not_found, idempotency_conflict, session_working, usage_limit_exceeded. Open."
        },
        "type": {
          "description": "Category, e.g. invalid_request_error, authentication_error, usage_limit_error. Open.",
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "host": {
          "description": "host_in_use: the host another service holds.",
          "type": "string"
        },
        "service": {
          "description": "host_in_use: the id of the service that holds the host.",
          "type": "string"
        }
      },
      "required": [
        "code"
      ]
    }
  },
  "required": [
    "error"
  ]
}
```

## Authentication

```json
{
  "bearerAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}
```
