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:

json
{
  "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 (no file://, no ssh://, no data:)
  • 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:

json
{ "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

text
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

text
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.

text
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.