Skip to content

HTTP API

Set the broker address in MINT_BASE_URL. Use a loopback example when you test locally.

Terminal window
export MINT_BASE_URL=http://127.0.0.1:4949
curl --fail "$MINT_BASE_URL/healthz"

Mint has one unauthenticated health route and two authenticated effect routes. It does not provide a try-it console.

MethodPathAuthenticationPurpose
GET/healthzNoneReport audit readiness and declaration generation.
Any method/proxy/{scheme}/{host}/{*rest}RequiredForward a policy-approved request after placeholder resolution and response scrubbing.
POST/action/{action_id}RequiredRun a registered typed action with fixed input and output schemas.

The path parameters are literal route components. The proxy preserves the query after canonical decoding and re-encoding.

Call the health route without an authentication header.

Terminal window
curl --include "$MINT_BASE_URL/healthz"

A ready broker returns 200 with an ok line. The line includes a backlog count and value-free declaration generation fingerprints. An audit gate that is not ready returns 503. The 503 body says that no effect was attempted. During graceful drain, 503 includes Retry-After.

Health does not resolve credentials or contact an upstream service.

The broker selects authentication from MINT_AUTH_MODE.

ConfigurationBehavior
UnsetShared-secret mode for zero-configuration local development.
shared-secret or devShared-secret mode.
tailnetTailnet identity mode.
Any other set value, including an empty valueTailnet mode.

Leading and trailing ASCII whitespace is ignored. Unknown values fail toward tailnet authentication.

Send X-Mint-Capability with a capability token from the declaration file. Mint verifies the stored hash, expiry, and scope. The connection must come from loopback.

Terminal window
curl --include -H 'X-Mint-Capability: <synthetic-capability>' "$MINT_BASE_URL/proxy/https/example.com/v1/status"

The broker does not forward X-Mint-Capability upstream. Never place a real token in source, documentation, or logs.

Mint resolves identity from the request peer address. It does not trust a caller-supplied identity header. Tailnet mode requires an empty capability declaration. Authentication failures return 401.

The proxy accepts any HTTP method. A request uses this shape:

<METHOD> $MINT_BASE_URL/proxy/<scheme>/<host>/<path>

For example, this request sends a synthetic placeholder in an authorization value:

Terminal window
curl --include -X POST -H 'X-Mint-Capability: <synthetic-capability>' -H 'Authorization: Bearer __mint.example.default__' -H 'Content-Type: application/json' --data '{"query":"status"}' "$MINT_BASE_URL/proxy/https/example.com/v1/status"

Mint authenticates the caller before policy evaluation. It evaluates actor, workspace, service, method, and path with first-match policy rules. A denied rule prevents target resolution and upstream contact. The rule must permit the URL scheme. An alias used as a header injection slot must also be permitted by the rule.

Mint resolves each unique alias once per request. It removes its own authentication header before forwarding. It removes hop-by-hop headers and forces Accept-Encoding to identity upstream. Redirects are disabled. DNS results are pinned before the upstream connection. Private network targets are denied unless the matched rule allows them.

Use a placeholder where a credential value would otherwise appear. The grammar is mint.<service>.<name>. A policy alias uses secret://<service>/<name>.

Authorization: Bearer __mint.example.default__

Policy must allow the matching alias before Mint injects a credential. An alias in a request cannot broaden the matched rule.

A header value is always an injection slot. An unpermitted alias in a header denies the whole call with 403 and the reason alias_not_permitted_by_rule.

A body value or a query value is not always an injection slot. A well-formed placeholder there resolves only when its alias is permitted. An unpermitted alias in a body or a query value stays inert literal text and forwards verbatim, exactly like a malformed placeholder. Mint never queries the credential store for that alias. A request may mix a permitted alias with an unpermitted one: the permitted alias still resolves, and the unpermitted mention still forwards as literal text.

This split exists because a request body or a query string often quotes placeholder syntax as prose, for example an agent's own prompt that describes Mint. A header never legitimately quotes a credential as prose. A permitted alias that appears as prose in a body or a query value still resolves and still injects the real secret. Avoiding that is a caller responsibility Mint does not yet enforce.

Mint can substitute placeholders in header values, query values, text bodies, form values, and eligible JSON values. JSON object keys are not credential slots. An embedded JSON placeholder is eligible in a credential-shaped field. A complete placeholder scalar is eligible as a value.

A rule that requires placeholders rejects credential-shaped literals in query strings and bodies. It also rejects literal credentials in credential-shaped headers. Mixed literal and placeholder values are rejected after substitution. These checks run before credential resolution whenever possible.

The broker applies a 2 MiB inbound body limit to the router. The same bound applies after placeholder substitution. A resolved header value is checked against the broker bound. A resolved URI is checked against the broker bound.

The default upstream response limit is 8 MiB. Set MINT_MAX_RESPONSE_BODY_BYTES to change that limit. The response limit applies to decoded bytes. Streaming SSE frames use the same configured bound per frame.

A request body that exceeds the bound returns 413. A resolved header that exceeds the bound returns 431. A resolved URI that exceeds the bound returns 414. A response that exceeds the configured bound returns 502.

Mint scrubs known resolved bytes from response headers and bodies. It also scrubs credential-shaped fields and formats. A rule may set never_relay_credentials for a stronger deny decision. That rule returns 502 when the response carries a detected credential.

Mint returns the upstream status after successful bounded reading and scrubbing. It removes Content-Length when the body changed. It removes Content-Encoding after decoding.

Mint requests identity encoding from the upstream. A response with text/event-stream and no compression can stream incrementally. Mint reads enough bytes to reject mislabeled compressed or opaque bodies before streaming. The SSE scrubber waits for complete frames and preserves event boundaries. An oversized or invalid UTF-8 frame fails closed.

Mint decodes a single gzip, x-gzip, or deflate response before scrubbing. It bounds the decoded size. Deflate decoding tries zlib-wrapped bytes and then raw deflate bytes. Mint also detects gzip or zlib magic bytes when an upstream ignores identity encoding. Decoded responses are buffered rather than streamed.

Mint refuses stacked encodings and unsupported encodings such as br or zstd. It refuses opaque compressed formats that it cannot decode safely. Those responses return 502.

The action route accepts only POST. The current action id is firecrawl.interact_prompt. No other action id is registered.

Terminal window
curl --include -X POST -H 'X-Mint-Capability: <synthetic-capability>' -H 'Content-Type: application/json' --data '{"url":"https://example.com","prompt":"Summarize the page"}' "$MINT_BASE_URL/action/firecrawl.interact_prompt"

The input object has exactly two fields. Both fields are strings. The URL must be non-empty, start with https://, and be at most 2048 bytes. The prompt must be non-empty and at most 4096 bytes. Unknown fields are rejected. The request body limit for this action is 8192 bytes.

Mint resolves the fixed firecrawl/default alias after audit preparation. It performs the fixed scrape, one interact call, and session stop sequence. It does not expose the vendor session identifier or live-view handles.

A successful response has only these fields:

{"success":true,"output":"A bounded semantic answer."}

The output is capped at 16 KiB and receives credential-shaped scrubbing. The action upstream response reads are capped at 1 MiB.

The following statuses are generated by Mint. A successful proxy call may instead return the upstream status.

StatusMeaning
200Health is ready, or an effect completed with a successful upstream result.
400Ambiguous request target, non-canonical query, invalid method, or invalid typed-action JSON.
401Authentication failed.
403Policy denied the call, scheme, private egress, a header alias, or a credential-shaped literal.
404The typed action id is not registered.
413An inbound or resolved request body exceeds its limit.
414A resolved request URI exceeds its limit.
431A resolved request header exceeds its limit.
429A call guard rejected the request.
500Mint could not build an upstream client or streaming response.
502Mint could not resolve, forward, read, decode, scrub, or bound the response.
503The audit gate is not ready, the upstream circuit is open, or the effect outcome is indeterminate.
504The upstream request or response body exceeded its timeout.

The typed action maps vendor-step failures to bounded 502 or 504 responses. The action never returns a raw vendor response body.

Route registration, the health response, the request limit, declaration selection, and server lifecycle are pinned in broker lib.rs and lib.rs server setup. Authentication modes and loopback enforcement are pinned in config.rs and auth.rs. Proxy admission, policy, placeholders, forwarding, and status paths are pinned in proxy.rs admission, proxy.rs substitution, and proxy.rs response handling. Header placeholder aliases are authorized or denied at proxy.rs alias check; an unpermitted alias in a body or query value is left inert at proxy.rs body and proxy.rs query. Compression decoding and streaming bounds are pinned in stream.rs and proxy.rs decode helper. The typed action registry and schema are pinned in typed_action.rs, with route validation and fixed execution in typed_effect.rs and typed_effect.rs execution.