Install
openclaw skills install @psyb0t/proxqGo, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.
openclaw skills install @psyb0t/proxqThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. "I'll get back to you" as a service — every HTTP request becomes an async job in a Redis-backed queue (via asynq).
For installation, configuration, and container setup, see references/setup.md.
upstreams[].url is configured (and, via directProxyMode/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.PROXQ_URL can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.upstreams[].url to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus X-Forwarded-For/X-Real-IP/X-Forwarded-Proto — treat the upstream config the same way you'd treat a reverse-proxy target list.DELETE /__jobs/{id} best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.pathFilter, slow paths get queued.directProxyMode in setup.md), so don't expect a job ID for them.Point at a running instance:
export PROXQ_URL=http://localhost:8080
All job-management endpoints live under jobsPath (default /__jobs). Every response proxq itself generates (not proxied from upstream) carries X-Proxq-Source: proxq — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.
Any request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for direct-proxy bypass — see setup.md).
curl -s -X POST "$PROXQ_URL/api/heavy-computation" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer upstream-token" \
-d '{"data": "lots of it"}'
# 202 Accepted, X-Proxq-Source: proxq
# {"jobId": "550e8400-e29b-41d4-a716-446655440000"}
If no upstream prefix matches the request path: 502 Bad Gateway, X-Proxq-Source: proxq.
Optional per-request override header: X-Proxq-Timeout: <go-duration> (e.g. X-Proxq-Timeout: 30s) overrides the upstream's configured timeout for that one request. Invalid value → 400 Bad Request.
curl -s "$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000"
{"id": "550e8400-...", "status": "completed", "completedAt": "2025-01-01T00:00:00Z"}
Failed job includes error:
{"id": "550e8400-...", "status": "failed", "error": "forward request: dial tcp: connection refused"}
status is one of queued (pending/scheduled/aggregating), running (active, or waiting on a retry), completed (done — response stored, even if upstream returned 4xx/5xx), failed (transport broke and retries are exhausted). Unknown job ID → 404 Not Found, X-Proxq-Source: proxq, body {"code":"NOT_FOUND","message":"Not found"}.
Replays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.
curl -si "$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content"
HTTP/1.1 200 OK
Content-Type: application/json
X-Custom-Header: from-upstream
{"result": "done"}
If upstream returned a 404, you get 404 back too — but without X-Proxq-Source (it's a real upstream response). If the job isn't done yet, or doesn't exist: 404 Not Found with X-Proxq-Source: proxq. That header is the whole disambiguation trick — present means "proxq talking", absent means "upstream talking".
curl -s -X DELETE "$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000"
# {"status": "cancelled"}
Best-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → 404 Not Found, X-Proxq-Source: proxq. (See Security & safety — no ownership check, so only cancel jobs you own.)
JOB_ID=$(curl -s -X POST "$PROXQ_URL/api/report" -d '{}' | jq -r .jobId)
while :; do
STATUS=$(curl -s "$PROXQ_URL/__jobs/$JOB_ID" | jq -r .status)
case "$STATUS" in
completed) curl -s "$PROXQ_URL/__jobs/$JOB_ID/content" | jq; break ;;
failed) echo "job failed" >&2; break ;;
*) sleep 2 ;;
esac
done
For upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see references/setup.md.