Add a tool endpoint
Adds a route you serve and starts a sync. Sending the same URL again re-syncs that endpoint. A different URL adds another endpoint. Tool calls use signing material derived from a Median key, so this creates no second credential.
Called with a Median key, the endpoint is signed with that key. Called with an OAuth token, it keeps the key it already had, or takes the newest key. An OAuth token needs an admin or owner. Adding or re-pointing an endpoint does not count against the sync limit.
PUT
/tool-endpointsAuthorization
AuthorizationBearer token · headerrequiredA MEDIAN_KEY from Settings under API. It starts with `median_key_` and stays on your server. The tool endpoint routes also accept an OAuth access token (`median_oat_`) from `median login` or an MCP client, acting as the person who approved it. The messaging routes accept only a Median key.
Request body
requiredapplication/jsonurlstringrequiredAn HTTPS URL on a public address, up to 512 characters. `http://localhost` and `http://127.0.0.1` pass validation, but Median calls your endpoint from the cloud, so the sync fails. Put a public HTTPS tunnel in front of your dev server instead.
max length 512
Responses
200The endpoint was added or re-synced.
okbooleanrequiredidstringrequiredThe connected endpoint's id.
400`invalid_json` or `invalid_request`: the body is not `{ "url": string }`. `invalid_url`: not a valid URL, over 512 characters, not HTTPS, or a private address. `missing_median_key`: the organization has no Median key yet. `too_many_tool_endpoints`: 10 endpoints are connected. `legacy_api_key`: a key from before Median keys was sent.
errorobjectrequiredShow propertiesHide properties
codestringrequiredBranch on this rather than on the message.
messagestringrequired401`missing_api_key`: no bearer token. `invalid_api_key`: the key matches no organization or was revoked. `publishable_key`: a `median_pk_` key was sent. `invalid_token`: the OAuth token is unknown or expired.
errorobjectrequiredShow propertiesHide properties
codestringrequiredBranch on this rather than on the message.
messagestringrequired403With an OAuth token, `forbidden` when the person is not an admin or owner, and `no_organization` when no organization is bound.
errorobjectrequiredShow propertiesHide properties
codestringrequiredBranch on this rather than on the message.
messagestringrequired429The organization's API allowance for this class of request is used up. Wait the `Retry-After` header's seconds. Limits depend on the plan. See [rate limits](/api/errors-and-limits#rate-limits).
errorobjectrequiredShow propertiesHide properties
codestringrequiredBranch on this rather than on the message.
messagestringrequiredRequest
curl -X PUT "https://api.median.sh/v1/tool-endpoints" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/api/median/tools"
}'const response = await fetch("https://api.median.sh/v1/tool-endpoints", {
method: "PUT",
headers: {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://example.com/api/median/tools"
})
});Response
{
"ok": true,
"id": "k170abc123"
}{
"error": {
"code": "invalid_url",
"message": "Use an HTTPS URL, or HTTP on localhost for testing."
}
}{
"error": {
"code": "invalid_api_key",
"message": "That Median key does not match any organization. Copy the median_key_ key again from Settings under API."
}
}{
"error": {
"code": "string",
"message": "string"
}
}{
"error": {
"code": "rate_limited",
"message": "Your organization's API allowance is temporarily full. Please retry shortly."
}
}