Skip to main content

Request API

POST /api/v1/requests performs one upstream HTTP or HTTPS request.

curl -sS http://localhost:8080/api/v1/requests \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-deployment-token' \
-d '{
"method": "POST",
"url": "https://httpbingo.org/anything",
"headers": [{"name":"Content-Type","value_base64":"YXBwbGljYXRpb24vanNvbg=="}],
"body": {"mode":"inline_base64","data_base64":"eyJoZWxsbyI6IndvcmxkIn0="},
"routing": {
"tags": ["datacenter"],
"country": "AU",
"region": "ap-southeast-2",
"ip_type": "residential",
"sticky_session_id": "checkout-session-42"
},
"timeout_ms": 30000,
"replayable": false
}'

Request fields​

FieldRequiredDescription
methodyesHTTP method.
urlyesAbsolute http or https URL. URL user info is rejected.
headersnoOrdered {name,value_base64} entries. Duplicate names are preserved.
bodynoInline {mode:"inline_base64",data_base64:"..."} or verified {mode:"receipt",receipt_id:"..."}.
routingnoWorker-routing constraints. Omit it when the deployment's ordinary route should choose the worker.
routing.tagsnoNon-empty unique string tags required of the worker. Up to 32 tags; each is at most 64 bytes.
routing.countrynoTwo-letter ISO country code constraint; Straw normalizes it to uppercase.
routing.regionnoNon-empty region identifier constraint, up to 128 bytes.
routing.ip_typenoNon-empty worker IP-type constraint, up to 128 bytes.
routing.sticky_session_idnoSession identifier used to prefer the same eligible worker while its rule pin remains valid.
response_body_modenoinline_base64 (default) or receipt when the object-storage profile is enabled.
fingerprint_profilenoExact, case-sensitive built-in profile name. Omit it for ordinary TLS. See the fingerprint catalogue.
timeout_msnoTotal deadline, from 1000 ms through the configured maximum.
replayablenoWire default is false and Control never silently changes it. Tagged Go and Python clients default GET, HEAD, and OPTIONS to true.

Validation, defaults, and limits​

The request endpoint accepts one strict JSON object. Unknown fields, trailing JSON values, invalid UTF-8, and an envelope larger than 4 MiB are rejected before dispatch. The following validation applies before routing:

  • method must be uppercase and one of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, or TRACE. CONNECT belongs to the authenticated forward-proxy ingress and is rejected by this REST endpoint.
  • url must be an absolute http or https URL. User information, fragments, IPv6 zone identifiers, empty hosts, malformed hostnames, and unsupported schemes are rejected.
  • headers may contain at most 64 ordered entries. Each name is at most 64 bytes; the aggregate of name bytes and decoded value bytes is at most 16,384 bytes. Values use standard base64 and may not contain CR or LF before or after decoding. Host, hop-by-hop headers, and proxy credentials are managed or rejected.
  • Omit body for no body. An inline body uses standard base64 and is limited by control.request.max_inline_request_body_bytes (1 MiB by default). A receipt body requires receipt_id and no inline data; it is limited by the receipt profile instead.
  • Omit timeout_ms to use the active runtime snapshot's default_timeout_ms (60 seconds by default). Static control.request.max_timeout_ms caps that value and explicit timeouts; its default cap is 120 seconds. An explicit timeout must be at least 1000 ms.
  • response_body_mode:"receipt" requires object storage to be enabled. Otherwise, the default inline response is bounded by control.request.max_inline_response_body_bytes (1 MiB by default) and an oversized response fails with body_too_large rather than returning a partial body.

The REST wire field replayable defaults to false. Go and Python SDKs apply the safe-method default before sending; they do not retry automatically. Set it explicitly only when replaying the operation is safe for the destination.

Hop-by-hop headers, Host, Content-Length, and proxy authorization headers are managed or rejected by Straw. Request bodies default to a 1 MiB limit. That limit applies to inline bodies; receipt bodies use object_storage.max_object_bytes and must pass the receipt size/checksum flow before assignment. See Object storage and receipts.

Fingerprinting controls TLS ClientHello and, when negotiated, HTTP/2 settings, flow-control, pseudo-header ordering, and priority behavior. It does not synthesize browser application headers, cookies, JavaScript, or browser state. HTTP/3 is not supported. A named request fails with unsupported_fingerprint if the selected worker does not advertise the exact profile.

Routing behavior​

Routing rules are evaluated in ascending priority order. A configured rule constraint must be present in the request and match; an omitted routing value does not silently satisfy a configured country, region, ip_type, ingress, or host constraint. When a routing hint is supplied, the selected worker must advertise the corresponding capability; a worker with a missing capability claim is not treated as a wildcard. Tags require the worker to advertise every requested tag.

sticky_session_id pins selection to the worker previously selected for that deployment and session while the rule's sticky TTL is valid. If that worker is unavailable, the rule's allow_sticky_fallback setting determines whether Straw may select another eligible worker or returns sticky_session_unavailable. If every otherwise eligible worker is at capacity, Straw returns executor_capacity_exhausted. With no routing hints, normal rule priority and worker capacity selection apply.

The same RoutingHints contract is available to the forward-proxy ingress through the authenticated X-Straw-Route-Tags, X-Straw-Route-Country, X-Straw-Route-Region, X-Straw-Route-IP-Type, and X-Straw-Route-Sticky-Session headers. See HTTP and HTTPS proxy ingress for the bounded syntax and header-stripping rules.

REST, absolute-form proxy, and CONNECT requests evaluate the same deployment routing rules and pool/capability constraints. Only an explicit ingress rule or worker ingress capability differentiates those modes. replayable is an explicit transport-retry permission: clients default GET, HEAD, and OPTIONS to true, while other methods remain false unless the caller opts in and the operation is safe to repeat.

Sticky upstream-proxy affinity​

Proxy-backed routes have two independent stickiness layers:

LayerOwnerBehavior
Straw worker pinControl(deployment_id, sticky_session_id) prefers the same eligible worker for the selected rule's sliding sticky TTL.
Provider sessionUpstream providerA pool/profile/geo-scoped session token asks the provider to retain an exit according to its own contract.

Control derives a provider session only when sticky_session_id is non-empty, the selected rule has a positive sticky_session_ttl_seconds, and the selected pool uses an upstream proxy. It is the first 128 bits of a deterministic SHA-256 derivation over the deployment, selected pool, upstream proxy ID, normalized country/region/IP type, and caller sticky ID, encoded as exactly 32 lowercase hexadecimal characters. This makes it stable across Control replicas and eligible workers without disclosing the raw caller value. It is pseudonymous, not encrypted; use opaque sticky IDs and never put personal data in them.

An ordinary request and an Egress internal retry preserve the profile and provider session. If the pinned worker is unavailable, disabled sticky fallback returns sticky_session_unavailable. Enabled fallback within the same pool sends the same provider session to another exactly matching worker. Fallback to a different pool derives a new provider session and resets provider affinity. Workers in one pool must therefore use operationally equivalent provider accounts, zones, templates, and session namespaces; Control can verify only the profile ID claim, not worker-local secrets or endpoints.

effective affinity <= min(active Straw route pin, provider session retention)

The routing TTL does not force provider rotation. Reusing the same caller sticky ID later derives the same pseudonymous value, but the provider may have expired or remapped it. Straw does not promise exact-IP persistence beyond the provider's documented retention behavior. Callers continue to select routes through existing hints and cannot submit a proxy profile ID or provider credentials.

Success​

Control returns HTTP 200 when Straw transported the request, even if the destination returned an error status.

{
"request_id": "req_...",
"status": 200,
"headers": [{"name":"Content-Type","value_base64":"dGV4dC9odG1s"}],
"body": {"mode":"inline_base64","data_base64":"...","truncated":false},
"timing": {"routing_ms":0,"assignment_ms":1,"egress_ms":82,"total_ms":84}
}

The timing phases map onto the stages a request passes through:

body.truncated is reserved for compatibility and is currently false. If the upstream body exceeds max_inline_response_body_bytes, Straw returns body_too_large instead of a partial success.

With response_body_mode:"receipt", body instead contains mode, receipt_id, size_bytes, and sha256_hex. The authorized content endpoint and explicit expiry are returned by GET /api/v1/receipts/{receipt_id}.

Errors​

Straw failures use an outer 4xx or 5xx status and a stable envelope:

{
"category": "client",
"code": "invalid_request",
"message": "request URL must use http or https",
"retryable": false,
"request_id": "req_..."
}

Optional fields are timeout_type, retry_after_ms, upstream_status, and details. upstream_status is present only when an upstream CONNECT gateway returned a status, such as 407; it is not the destination response status. Use code for program logic and message for humans. Retry only when retryable is true and the original operation is safe to replay.

details is a bounded string map intended for diagnostics, not program-wide branching. The currently documented detail values are:

ErrorDetailValues or meaning
body_too_large during request validationdirectionrequest
body_too_large during request validationlimit_bytesactive inline request-body limit
timeout errorstimeout_typeassignment_timeout, connect_timeout, response_header_timeout, idle_timeout, upload_timeout, download_timeout, or total_deadline_timeout
upstream_proxy_failurefactfixed safe CONNECT phase fact; see Troubleshooting

Response-body size failures may omit details; use the stable error code and configured response limit. An upstream HTTP error status is still a successful Straw transport: it appears in the response envelope's status field while the outer HTTP status remains 200.

Stable codeCategoryHTTP / retryableMeaning
auth_failureclient401 / noinvalid deployment token
invalid_request, header_injection_failed, unsupported_ingress_modeclient400 / nomalformed input or unsupported request behavior
destination_deniedclient403 / nodestination policy denied the target
route_no_matchrouting404 / nono routing rule matched
route_unavailable, executor_capacity_exhaustedrouting503 / yesno eligible capacity currently exists
sticky_session_unavailablerouting503 / norequired sticky worker is unavailable
assignment_timeout, transport_unavailabletransport504 / yesassignment/NATS did not complete
worker_disconnectedtransport502 / yesworker disappeared mid-request
protocol_error, unsupported_fingerprinttransport502 or 400 / noinvalid protocol sequence or unsupported requested profile
timeout_exceededtransport504 / nototal deadline expired; timeout_type identifies the stage
upstream_dns_failure, upstream_tls_failure, upstream_connection_refused, upstream_reset, upstream_proxy_failureegress502 / yesupstream resolution, TLS, connection, reset, or proxy failure
upstream_connect_timeoutegress504 / yesupstream connection deadline expired
stream_upload_aborted, stream_download_abortedstreaming502 / nobounded stream was interrupted
body_ref_unavailablestreaming409 / noreceipt is missing, expired, or ineligible
body_too_largestreaming413 / noconfigured inline/request/response limit exceeded
control_internal_errorcontrol500 / nounexpected Control failure
executor_internal_erroregress502 / nounexpected Egress failure
cancelledclient499 / nocaller or operator cancelled the request

The table is generated from the semantic registry contract: changing any code requires compatibility notes and the public-surface drift check verifies every registry string remains represented here.