GuideAPI Proxies (CORS bypass)

API Proxies (CORS bypass for artifacts)

Many enterprise REST APIs (Shortcut, Linear, Jira, …) don’t return Access-Control-Allow-Origin headers. A Sandpack artifact running inside https://*.codesandbox.io therefore can’t call them directly from the browser — the browser’s CORS check fails before any response is seen, regardless of headers, preflight, or library used.

The Agor daemon ships a thin HTTP proxy to solve this. Anything sent to /proxies/<vendor>/X is forwarded byte-for-byte to <upstream>/X. The daemon already accepts requests from *.codesandbox.io, so artifact code sees a normal CORS response.

What this is, and is not

This is a dumb pass-through proxy:

  • ✅ Pass-through bytes only — no transformation, no schema awareness, no caching.
  • ✅ Read-only by default — allowed_methods defaults to [GET].
  • ✅ Off by default — no proxies: block in config.yaml means the route is not mounted at all.
  • No auth injection. The daemon does not read your env vars and does not set auth headers. Auth is your responsibility — set it in agor.config.js and pass it on every request.
  • ❌ No vendor presets. Configuration is yaml-driven; the daemon ships no built-in adapters for Shortcut/Linear/etc.

If you want OAuth flows, token refresh, response caching, or schema-aware routing, this proxy is the wrong layer — wire those into the artifact or a dedicated backend.

Configuration

Add a proxies: block to ~/.agor/config.yaml:

proxies:
  shortcut:
    upstream: https://api.app.shortcut.com
    description: Shortcut REST API
    docs_url: https://developer.shortcut.com
    allowed_methods: [GET]
 
  linear:
    upstream: https://api.linear.app
    description: Linear GraphQL API
    docs_url: https://developer.linear.app
    allowed_methods: [GET, POST]   # GraphQL needs POST
 
  jira:
    upstream: https://your-org.atlassian.net
    description: Jira REST API v3
    docs_url: https://developer.atlassian.com/cloud/jira/platform/rest/v3/
    allowed_methods: [GET]

Field reference

FieldRequiredDefaultNotes
upstreamyesBare scheme+host, no path prefix. Must be https://.
descriptionnoSurfaced in MCP discovery (agor_proxies_list) and the UI.
docs_urlnoLink to the upstream’s developer docs. Surfaced alongside description.
allowed_methodsno["GET"]Subset of GET POST PUT PATCH DELETE HEAD.

The upstream convention

upstream is the bare host (scheme + hostname, no path). The artifact specifies the path tail. So with upstream: https://api.app.shortcut.com, a request to /proxies/shortcut/api/v3/projects lands at https://api.app.shortcut.com/api/v3/projects.

If the operator includes a path prefix in upstream and the caller specifies the same prefix, you get a double-prefix bug — that’s why we recommend the bare-host convention.

Security model

The daemon’s job is to bypass CORS. Auth is the artifact’s job.

The proxy:

  1. Requires authentication. Calls without a valid Agor JWT are rejected with 401.
  2. Strips inbound cookie so Agor session cookies cannot leak to third parties.
  3. Strips outbound set-cookie so the upstream cannot set cookies on the daemon’s domain.
  4. Forwards everything else as-is — including arbitrary Authorization and vendor-specific tokens (Shortcut-Token, X-Linear-Api-Key, etc.) that the artifact set intentionally.
  5. Rate-limits per (user, vendor) at 600 req/min by default — sized as a daemon-protection cap, not a workload tuner. Hits return 429 immediately (no queueing).
  6. Caps response bodies at 5 MB to keep a misbehaving upstream from exhausting daemon memory.
  7. Times out at 30 seconds for the entire upstream round-trip.

Unsuccessful upstream responses (404, 401, 5xx) are forwarded verbatim — the artifact wants to see Shortcut’s 401 if the token is wrong, etc.

Using a proxy from an artifact

The recommended pattern is to expose proxy URLs and secrets through the existing agor.config.js Handlebars template. Each user’s API token stays in their own environment variables; the template variable agor.proxies.<vendor>.url resolves to the daemon’s fully-qualified proxy URL.

// /agor.config.js
export const shortcutApi = "{{ agor.proxies.shortcut.url }}";
export const shortcutToken = "{{ user.env.SHORTCUT_API_TOKEN }}";

Then in the app:

// /App.js
import { shortcutApi, shortcutToken } from '/agor.config.js';
 
async function listEpics() {
  const res = await fetch(`${shortcutApi}/api/v3/epics`, {
    headers: { 'Shortcut-Token': shortcutToken },
  });
  if (!res.ok) throw new Error(`Shortcut API ${res.status}`);
  return res.json();
}

Available template variables:

VariableValue
{{ agor.proxies.<vendor>.url }}Fully-qualified proxy URL
{{ agor.proxies.<vendor>.upstream }}Configured upstream host
{{ agor.proxies.<vendor>.allowed_methods }}Methods the proxy accepts

Internal fields (rate limit settings, max body size, etc.) are not exposed in the template context.

Discovery (MCP)

Agents authoring artifacts can introspect configured proxies via two read-only MCP tools:

  • agor_proxies_list — returns all configured proxies with their URL, upstream, allowed methods, and description.
  • agor_proxies_get(vendor) — same shape, scalar. Errors if the vendor is not configured.

These tools never expose internal config (rate limit thresholds, max body size, etc.) — only the metadata an agent needs to write a working artifact.

Error responses

StatusBodyMeaning
401{"error":"unauthorized"}No / invalid Agor JWT.
404{"error":"unknown_vendor","vendor":"…"}Vendor slug not in proxies: config.
405{"error":"method_not_allowed", "method":"…", "allowed":[…]}Method not in allowed_methods. Allow header set.
413{"error":"request_too_large"}Inbound Content-Length > 5 MB.
429{"error":"rate_limited"}Per-(user, vendor) bucket exhausted.
502{"error":"upstream_error"}Network error, DNS failure, or 30 s timeout reached.
502{"error":"upstream_too_large"}Upstream Content-Length > 5 MB.

The proxy does not echo upstream error messages — only the upstream status code and body. Errors visible to the caller therefore come from one of two sources: the proxy itself (with the JSON shapes above) or the upstream (verbatim status + body).

Anti-patterns

  • ❌ Don’t paste long-lived service tokens into proxies: config. The config is read by the daemon, not surfaced to artifacts — but it’s also not a secret store. Auth tokens belong in user environment variables.
  • ❌ Don’t add an * upstream or a “passthrough to anything” vendor entry. Each vendor is a deliberate, audited choice.
  • ❌ Don’t proxy plaintext (http://) upstreams. Daemon startup rejects these to keep operators from accidentally relaying credentials over the wire.

See also

BSL 1.1 © 2026 Maxime Beauchemin