Jettson HTTP
Outbound HTTP requests with SSRF guards. The right tool when the data lives behind an API, not a web page.
When the data an agent needs lives behind an HTTP API — Stripe, GitHub, Slack, your own services — it reaches out with jettson_http_request. Private IP ranges and Jettson-owned domains are refused at the proxy boundary.
jettson_http_request
| Field | Type | Description |
| --- | --- | --- |
| method (required) | string | HTTP method. GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. |
| url (required) | string | Absolute http(s) URL. |
| headers | object | Optional request headers. String values only. |
| body | string | Optional UTF-8 request body. JSON-stringify structured data before passing. |
| timeout_seconds | number | Per-call timeout (default 30). Capped server-side at 30. |
Returns:
{
"status_code": 200,
"headers": { "content-type": "application/json", "..." },
"body": "{ \"items\": [...] }",
"truncated": false
}truncated: true if the response body exceeded 100 KB. The first 100 KB is returned; for larger payloads use jettson_shell_run with curl (subject to the shell's 10 MB cap).
What's blocked
- Non-
http(s)schemes (nofile://, nossh://, nodata:) - Hostnames resolving to RFC 1918 / loopback / link-local / multicast / reserved IP space
- Any hostname ending in
.jettson.dev(no calling our own API as yourself)
Blocked requests return:
{ "error": "Jettson HTTP rejected the request: destination resolves to a private/internal address." }The SSRF guard runs against DNS resolution before the request leaves the container — there's no way to bypass it via DNS rebinding either.
Examples
Calling a public API
jettson_http_request({
method: "GET",
url: "https://api.github.com/repos/jettson/jettson-runtime",
headers: { "Authorization": "token <token-from-task-context>", "Accept": "application/vnd.github.v3+json" }
})POSTing to a webhook
jettson_http_request({
method: "POST",
url: "https://hooks.slack.com/services/T.../B.../...",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Triage summary: 5 events, 1 critical." })
})Walking pagination
The Mind handles pagination itself — each jettson_http_request call gets a 100KB response and the loop continues until the cursor runs out.
1. jettson_http_request({ method: "GET", url: "https://api.example.com/items?limit=100" })
2. (Mind sees `next_cursor` in body, repeats with `?limit=100&cursor=...`)
3. (...until no next_cursor)Headers worth knowing
Jettson HTTP follows redirects by default (up to the underlying library's limit, usually 30). To inspect the redirect chain, send HEAD first or set a custom header — there's no follow_redirects: false knob today.
The Content-Length of the request body is set automatically from the body parameter — don't set it manually.
Failure modes
| Situation | Result |
| --- | --- |
| Bad scheme / not http(s) | error: "only http(s) URLs are permitted." |
| DNS resolution → private IP | error: "destination resolves to a private/internal address." |
| Hostname is *.jettson.dev | error: "destination host is not allowed." |
| Connection refused | error: "could not connect to the destination." |
| Wall-clock timeout (30s) | error: "request timed out after 30s." |
| Response > 100 KB | Body truncated; truncated: true. Use jettson_shell_run for larger pulls. |
The HTTP tool doesn't retry. If the agent prompts asks for retries, the Mind will loop — but jettson_http_request itself is one-shot.
Related
- Browser — when there's no API, scrape instead
- Tool composition patterns —
http + memoryandhttp + filesrecipes