HTTP Tools & OAuth
Call any OAuth-authenticated API — Gmail, Google Calendar, Twitter/X, Stripe, Slack, or your own — with typed schemas, automatic token injection, and generative UI out of the box.
Why this matters#
A generic shell or curl tool lets the LLM guess flags, guess endpoints, and improvise auth headers. HTTP tools take a different path: the agent declares the endpoint ahead of time, with a typed parameter schema and a body template. The runtime handles auth, labels, and UI.
| Generic shell/curl | http_tools | |
|---|---|---|
| Schema | Freeform string input | Typed params the LLM can reason over |
| Auth | LLM improvises headers | Tokens injected automatically from the user's own accounts |
| Labels | Generic "Running command" | Branded running/finished strings per tool |
| UI | Text output | Optional generative UI components per state |
| Blast radius | Any URL, any method | Only the declared endpoint |
This is how a Rush agent hits the user's real Gmail inbox or real Twitter account — not a sandbox, not scraping, not an API key in an env var. The user connects their account once; every agent that needs it picks up the token.
Declare an HTTP tool#
Add an http_tools block to agent.yaml. Each key is the tool name the LLM will see. Here is the twitter_post_tweet tool lifted directly from agents/content-writer/agent.yaml:
http_tools:
twitter_post_tweet:
description: "Post a tweet to Twitter/X"
endpoint: /api/v1/twitter/tweets
method: POST
auth: bearer
twitter_token: true
params:
text:
type: string
in: body
description: "The tweet text (max 280 characters)"
required: true
reply_to:
type: string
in: body
description: "Optional tweet ID to reply to"
required: falseWhen the LLM decides to post a tweet, the runtime builds the request, injects the user's Rush session token and their Twitter OAuth token, fires it at the proxy, and returns the parsed response.
All fields#
Full field list for HTTPToolSpec (source: harness/agent/types.go):
| Field | Required | Description |
|---|---|---|
description | yes | What the tool does. The LLM uses this to decide when to call it. |
endpoint | yes | API path. Paths starting with / are resolved against the proxy base URL. |
method | yes | HTTP method: GET, POST, PUT, PATCH, DELETE. |
auth | no | Authentication type: bearer, apikey, basic, or none. bearer injects the user's Rush session token. |
headers | no | Static headers to include on every call. |
params | no | Parameter schema. Each param has type, description, required, optional in (body/query/path), default, and hidden. |
body_template | no | Go text/template for the JSON request body. See the syntax section below. |
google_token | no | Boolean. Adds X-Google-Access-Token header. |
google_scopes | no | Required Google scopes (gmail, calendar, contacts, docs). The tool is filtered out if the user hasn't enabled the required scope. |
twitter_token | no | Boolean. Adds X-Twitter-Access-Token header. |
slack_token | no | Boolean. Adds X-Slack-Token header. |
stripe_token | no | Boolean. Adds X-Stripe-User-Id header (the Stripe connected-account ID). |
labels | no | Custom running/finished labels shown in the UI while the tool runs. |
ui_component | no | Map of state (running, completed, failed) to a generative UI component. |
async | no | Async polling config for long-running operations. |
response_format | no | Post-process response before handing it to the LLM. toon converts uniform arrays to tabular form. |
Only google_token, twitter_token, slack_token, and stripe_token exist on HTTPToolSpec today. Adding a new OAuth provider takes five small edits — see below.
Supported OAuth providers#
The providers below have both a header injection hook in createHTTPToolFromSpec and a server-side handler in prix/api/src/:
| Service | Token field | Injected header | Example endpoints |
|---|---|---|---|
| Gmail | google_token: true | X-Google-Access-Token | /api/v1/gmail/messages, /api/v1/gmail/send |
| Google Calendar | google_token: true | X-Google-Access-Token | /api/v1/gcal/events, /api/v1/gcal/calendars |
| Twitter / X | twitter_token: true | X-Twitter-Access-Token | /api/v1/twitter/tweets, /api/v1/twitter/followers |
| Slack | slack_token: true | X-Slack-Token | /api/v1/slack/chat.postMessage |
| Stripe | stripe_token: true | X-Stripe-User-Id | Stripe Connect routes (proxy-side) |
Google scopes (gmail, calendar, contacts, docs) are enforced per-tool. If a tool declares google_scopes: [gmail] and the user hasn't granted the Gmail scope, the tool is filtered out at build time — the LLM never sees it.
The older tools page mentioned shopify_token and linkedin_token. Neither exists on HTTPToolSpec today. LinkedIn calls currently go through a dedicated tool, not the generic http_tools injection path. If you need Shopify, add it as a new provider using the five-step flow below.
Automatic auth injection#
When the agent calls an HTTP tool, the runtime reads ~/.rush/user.yaml, resolves the two tokens the tool needs, and injects them as headers before the request leaves the machine. The proxy then unpacks those headers and forwards to the upstream API.
The session token (Authorization: Bearer) gates access to the proxy. The service-specific header (X-Twitter-Access-Token, X-Google-Access-Token, etc.) is what the proxy uses to call the upstream API on the user's behalf. Two tokens, one round-trip.
Tokens refresh mid-session. The runtime registers a header resolver per tool, so a tool called at minute 59 uses a fresh token even if the first call used one about to expire.
body_template syntax#
body_template uses Go's text/template syntax. Parameters from params are available by name.
Basic substitution
body_template: '{"text": "{{text}}"}'Optional fields
Use {{if param}}...{{end}} to include fields only when present. The leading comma goes inside the conditional so the output stays valid JSON:
body_template: '{
"text": "{{text}}"
{{if reply_to}}, "reply_to": "{{reply_to}}"{{end}}
{{if media_ids}}, "media_ids": {{media_ids}}{{end}}
}'Multi-line payloads
For richer bodies, keep the template readable and let Go's parser handle whitespace:
body_template: |
{
"to": "{{to}}",
"subject": "{{subject}}",
"body": "{{body}}"
{{if cc}}, "cc": "{{cc}}"{{end}}
{{if bcc}}, "bcc": "{{bcc}}"{{end}}
}String values must be quoted in your template — the renderer does not auto-quote. Numbers, booleans, and arrays should be emitted without quotes ({{max_results}} not "{{max_results}}").
Response formatting#
APIs often return verbose JSON the LLM doesn't need. Set response_format: toon to convert uniform arrays into a compact tabular form — same data, a fraction of the tokens:
twitter_get_followers:
description: "Get a sample of the user's Twitter followers"
endpoint: /api/v1/twitter/followers
method: GET
auth: bearer
twitter_token: true
response_format: toonFor larger reshaping, use an async config or post-process inside a subagent.
Connect once, use everywhere#
When a user installs an agent that declares twitter_token: true, Rush checks ~/.rush/user.yaml. If Twitter isn't connected, the app prompts for OAuth before the agent runs. Once connected, every agent on that machine that needs a Twitter token picks it up automatically — no per-agent credentials, no env vars, no re-auth.
All tokens live in ~/.rush/user.yaml (mode 0600) under service-specific keys (session, google, twitter, slack). See Account & Tokens for storage details.
Adding a new OAuth provider#
Five edits to extend HTTP tools with a new provider. The example below adds Slack; swap names accordingly.
Add token field to AgentBuilder
type AgentBuilder struct {
slackAccessToken string
}
func (b *AgentBuilder) WithSlackAccessToken(token string) *AgentBuilder {
b.slackAccessToken = token
return b
}Add field to HTTPToolSpec
type HTTPToolSpec struct {
SlackToken bool `json:"slack_token,omitempty" yaml:"slack_token,omitempty"`
}Inject the header in createHTTPToolFromSpec
if spec.SlackToken && b.slackAccessToken != "" {
httpTool.AddHeader("X-Slack-Token", b.slackAccessToken)
}Load and pass the token in rush/cli
if slackToken := GetSlackAccessToken(); slackToken != "" {
builder = builder.WithSlackAccessToken(slackToken)
}Use it in agent YAML
http_tools:
slack_post_message:
description: "Post a message to a Slack channel"
endpoint: /api/v1/slack/chat.postMessage
method: POST
auth: bearer
slack_token: true
params:
channel:
type: string
required: true
text:
type: string
required: trueDon't forget the matching proxy endpoint in prix/api/src/ that reads the header and calls the upstream API, plus an OAuth callback handler if the provider doesn't already have one.
Real example: Twitter poster agent#
Minimal agent with three Twitter HTTP tools — post a tweet, read the user's recent tweets, read their followers. This is a stripped-down version of agents/content-writer/agent.yaml:
name: twitter-poster
version: 1.0.0
description: Draft and post tweets with context from the user's timeline
prompt: |
You help the user post tweets to their Twitter/X account.
Before drafting, read their recent tweets with twitter_get_my_tweets
to match their voice. Confirm the draft before calling twitter_post_tweet.
tools:
- name: web_search
http_tools:
twitter_post_tweet:
description: "Post a tweet to Twitter/X"
endpoint: /api/v1/twitter/tweets
method: POST
auth: bearer
twitter_token: true
params:
text:
type: string
in: body
description: "The tweet text (max 280 characters)"
required: true
reply_to:
type: string
in: body
description: "Optional tweet ID to reply to"
required: false
twitter_get_my_tweets:
description: "Get the user's recent tweets with engagement metrics"
endpoint: /api/v1/twitter/tweets/me
method: GET
auth: bearer
twitter_token: true
params:
max_results:
type: integer
in: query
description: "Maximum number of tweets (1-100, default 20)"
required: false
twitter_get_followers:
description: "Sample the user's followers to understand the audience"
endpoint: /api/v1/twitter/followers
method: GET
auth: bearer
twitter_token: true
params:
max_results:
type: integer
in: query
required: falseInstall it, run rush run twitter-poster, and the runtime takes care of auth, param validation, response parsing, and UI. The LLM sees three neatly typed tools and decides when to use each.