Many companies already front their vendors with an API gateway that holds the credentials and decides who may call what. Graft can use it. You name the vendor hosts the gateway covers, where the gateway answers, and the one header your Graft deployment identifies itself with. From then on, when an agent asks for a covered vendor, the connection exists at once: nobody types a secret, the console shows the connection as Through your API gateway, and every call for that vendor relays through the gateway. A vendor the gateway does not cover is connected exactly as before, with a credential entered in the console. This is the gateway provider. It is open code, configured by the environment, and off until you set it.

The variables

Four settings, all or nothing. Graft refuses to start if some are set and some are not, and names the missing ones. One optional setting rides beside them and is refused on its own: Under Docker Compose these go in .env beside the model group; .env.example carries them commented out.

What the gateway receives

Graft carries the vendor URL in the path, the way API gateways route:
An authored tool calling GET https://api.unleashedsoftware.com/Products?pageSize=50 through a gateway at https://gateway.corp.example/graft arrives at the gateway as:
So the gateway needs one route per covered host, /graft/api.unleashedsoftware.com/* forwarded to https://api.unleashedsoftware.com/* with the vendor credential attached, and a rule that admits your deployment’s header. If a vendor’s URL has a port, the host segment carries it: api.vendor.example:8443. The vendor is always https, so the segment carries the host alone. Everything the proxy strips from a request before it reaches any vendor is stripped here too: authorization, cookie, x-api-key and the capability token in whatever position an SDK put it. The gateway never sees an agent’s token. The caller’s remaining headers arrive as sent, or under the prefix if you set one. What comes back from the gateway is the vendor’s answer, verbatim, with one exception: if the gateway echoes the identity header’s value in a response, Graft replaces it with [redacted:credential] and marks the response x-graft-redacted: credential, as it does for any vendor that quotes a key.

What stays Graft’s

The gateway holds the credential. Graft still holds the line around it:
  • The dry run stops writes at Graft’s proxy. While a tool is being authored, a POST never reaches the gateway; the proxy answers a preview of the request, headers named and not valued, with your identity header among the names.
  • The host set is judged before the relay. A call for a host outside the connection’s declared set is refused before the gateway is asked, and a private, loopback or metadata address is refused as a vendor host as it always was.
  • Scope is still per agent. The agent that asked gets the connection. Another agent of yours asking for the same vendor is told the connection exists and is not in its scope; you add it under Scope on that agent’s page. An agent cannot grow its own scope. If the same agent later proposes the same vendor with one more host the gateway covers, the connection’s host set widens to include it rather than a second connection appearing.
  • Approvals are unchanged. acquire asks before code is authored against the connection, and a tool that writes asks once before its first write. Reads do not ask, as everywhere in Graft.
  • Revoke holds. Revoking a gateway connection removes it from every agent and clears every approval. An agent asking again is refused and told to ask you; Reconnect on the connection’s card is the way back, since there is no credential to re-enter.
The proxy’s record of every call names the vendor host and path the call was for and relay: gateway as what carried it, never the identity header or its value.

One thing to know about private gateways

The proxy refuses to send a credential to a private address, and checks the address a vendor’s name resolves to before opening the socket. Your gateway is exempt from that check, by exact hostname, because you set its URL in the environment. A gateway at https://gateway.corp.internal on a private network works. The exemption applies to the relay leg alone: a vendor host an agent proposes is never exempt, even one that spells your gateway’s name, so a connection with a credential entered in the console is still judged on the address its host resolves to.

A worked example

Your company fronts Unleashed and Google’s APIs with a gateway at https://gateway.corp.example/graft, and the gateway admits callers by an X-Deployment-Token header. In .env:
Restart Graft. The boot line ends with providers gateway, keyring. Then, in your harness:
The agent calls request_connection for api.unleashedsoftware.com, and the answer comes back connected at once with provider: gateway; no link, nothing to type. Under Connections the card reads Through your API gateway, with no credential button. The agent runs acquire, you answer the build approval once, and the tool’s reads go Graft proxy → gateway → Unleashed, each one on the gateway’s own logs under your deployment’s identity. Ask for a vendor the gateway does not cover and nothing changes: the agent hands you a link, and you enter the credential in the console as before.

If it does not connect

  • graft refused to start: … The gateway provider is partially configured: one of the four is unset. The line names which.
  • GRAFT_GATEWAY_UPSTREAM_URL must be https under NODE_ENV=production: the published image runs as production; a plain-http gateway is for development only.
  • The agent gets a link instead of connected: the proposal named a host outside GRAFT_GATEWAY_HOSTS. Coverage is all-or-nothing per connection: every host the agent proposes must match, because the connection’s calls may go to each of them.
  • A tool’s call answers 401 from the gateway: the gateway did not accept GRAFT_GATEWAY_HEADER_VALUE. The gateway’s own answer passes through to the tool; the proxy’s record shows upstreamStatus: 401 and relay: gateway.
  • A tool’s call answers 502 upstream_unreachable: Graft could not reach the gateway at all. Check the URL and that the Graft container can route to it.
  • connection_not_in_scope in the agent’s answer: the connection exists for another of your agents. Tick it under that agent’s Scope.
  • connection_revoked: you revoked it. Reconnect on the connection’s card restores it.

Try it