Tether
DocsGitHub

The tool endpoint

Endpoint requirements

A service that posts JSON to a customer-supplied URL is a request forgery engine unless it is picky about the URL. Tether is picky, and refuses rather than warns.

The rules

RequirementRefused whenError
Absolute URLRelative or unparseableinvalid_endpoint
HTTPS schemehttp://, or any other schemeinvalid_endpoint
Public addressResolves to loopback, private, or link-localinvalid_endpoint
Resolvable hostDNS lookup failsinvalid_endpoint
Configured at allThe tenant has no endpointnot_configured

A refusal is returned to the model as a failed tool call, and logged with the reason. The turn continues and the assistant says the lookup failed — it does not crash.

Blocked address ranges

Checked against every address the hostname resolves to, not just the first:

RangeWhy
127.0.0.0/8, ::1, localhostTether's own loopback — its database, its admin surfaces
10.0.0.0/8RFC 1918
172.16.0.0/12RFC 1918
192.168.0.0/16RFC 1918
169.254.0.0/16Link-local, and the cloud instance metadata endpoint
0.0.0.0/8, ::"This network" and the unspecified address
100.64.0.0/10Shared address space, used for carrier-grade NAT and by many private networks
198.18.0.0/15Reserved for network benchmarking
224.0.0.0/4 and above, IPv6 ff00::/8Multicast and reserved
IPv6 link-local, site-local, unique-localThe v6 equivalents
::ffff:a.b.c.d, ::a.b.c.d, 64:ff9b::a.b.c.dIPv6 forms that carry an IPv4 address. They are judged by the IPv4 address inside, so [::ffff:169.254.169.254] is refused exactly like 169.254.169.254

Numeric spellings of an address, such as 2130706433 or 0x7f.0.0.1, are read as the address they are and refused the same way.

An unresolvable hostname is refused too, with the message Endpoint host could not be resolved. Refusing to guess is the point: the alternative is letting the request through and finding out where it went afterwards. A refused address answers Endpoint resolves to a private or loopback address. followed by a sentence saying Tether cannot reach a server on your own machine or network. The same answer is given to http://localhost:3000/..., which fails on the address before it fails on the scheme, because the address is the problem you cannot fix by changing http to https.

How the address check works

Developing on your laptop against a hosted Tether

Tether calls your endpoint from its own servers, so http://localhost:3000 cannot work however it is spelled: https://localhost is refused as a loopback address and http:// is refused as insecure. Give your local server a public HTTPS address instead. Any tunnel that forwards a public https:// address to a local port will do; run one against the port node server.js prints, then register the tunnel's address with the path your handler is mounted at:

tether init --endpoint https://your-tunnel-address.example/tether

The tunnel supplies the valid certificate the client requires. Tether's signature check still applies to every call, so the tunnel does not need to be private or authenticated.

Local development

This section is for running Tether itself on your machine. Both guards can be relaxed, and both should be off everywhere else:

{
  "ToolTransport": {
    "AllowInsecureEndpoint": true,
    "AllowPrivateNetworkEndpoint": true
  }
}

AllowInsecureEndpoint permits http://. AllowPrivateNetworkEndpoint disables the address check, which is what lets you point a local Tether at http://localhost:3000/tether/invoke.

These are process-wide, not per-tenant. Enabling either in a hosted environment turns the address guard off for every customer at once.

Practical requirements beyond the guard

A valid certificate chain. Self-signed certificates are rejected by the HTTP client, and there is no per-tenant trust store.

Reachable from Tether's egress. If your endpoint sits behind an IP allowlist, it needs the service's egress addresses.

Sized for bursts. One turn can dispatch up to four tool calls concurrently (AI:Orchestration:MaxConcurrentTools), and a turn can make several rounds of them.

Fast. 20 seconds per attempt is a ceiling, not a budget. See Returning results.

Changing the endpoint