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_methodsdefaults to[GET]. - ✅ Off by default — no
proxies:block inconfig.yamlmeans 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.jsand 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
| Field | Required | Default | Notes |
|---|---|---|---|
upstream | yes | — | Bare scheme+host, no path prefix. Must be https://. |
description | no | — | Surfaced in MCP discovery (agor_proxies_list) and the UI. |
docs_url | no | — | Link to the upstream’s developer docs. Surfaced alongside description. |
allowed_methods | no | ["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:
- Requires authentication. Calls without a valid Agor JWT are rejected
with
401. - Strips inbound
cookieso Agor session cookies cannot leak to third parties. - Strips outbound
set-cookieso the upstream cannot set cookies on the daemon’s domain. - Forwards everything else as-is — including arbitrary
Authorizationand vendor-specific tokens (Shortcut-Token,X-Linear-Api-Key, etc.) that the artifact set intentionally. - Rate-limits per (user, vendor) at 600 req/min by default — sized as
a daemon-protection cap, not a workload tuner. Hits return
429immediately (no queueing). - Caps response bodies at 5 MB to keep a misbehaving upstream from exhausting daemon memory.
- 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:
| Variable | Value |
|---|---|
{{ 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
| Status | Body | Meaning |
|---|---|---|
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
- Artifacts — Sandpack-based live web apps.
- Agor MCP Server — agent-facing introspection tools.