Skip to content
Browse documentation

Outbound Connections

Use canvasdrop.connections.fetch when a canvas needs data from a third-party HTTP API that cannot be called directly from the browser—for example, an API that does not allow browser CORS or requires a fixed User-Agent or API key.

Connections is a controlled request forwarder, not a Lambda runtime. Canvas Drop never runs canvas-supplied server code. An instance administrator creates a reusable profile for one exact HTTPS origin, chooses the allowed standard methods, stores any protected headers, and grants the profile to individual canvases. The protected values remain encrypted on the server.

Fetch through a profile

Suppose an administrator created the profile key stocks, approved https://api.example.com, allowed GET, fixed its User-Agent, and granted it to your canvas:

const symbol = "ACME";
const response = await canvasdrop.connections.fetch(
  "stocks",
  `/v2/quote?symbol=${encodeURIComponent(symbol)}`,
  { headers: { accept: "application/json" } },
);

if (!response.ok) {
  // This is the upstream's status and bounded response body, not a platform error.
  throw new Error(`Stock provider returned ${response.status}`);
}
const quote = await response.json();

The signature is:

connections.fetch(
  profile: string,
  path: string,
  init?: {
    method?: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
    headers?: RequestInit["headers"];
    body?: RequestInit["body"];
    signal?: RequestInit["signal"];
  },
): Promise<Response>

path must start with one /. Absolute URLs and protocol-relative paths are rejected in the SDK before a request is sent. The path and query are appended to the profile's exact origin. Canvas code cannot change its scheme, hostname, or port.

Only a method the administrator selected for that profile is accepted. Request bodies work for POST, PUT, PATCH, and DELETE; GET and HEAD do not send one. headers accepts the normal browser HeadersInit shapes. Canvas cookies, authorization, forwarding, hop-by-hop, compression, and host headers are never passed through. Supplying a caller header with the same name as a protected profile header is rejected; the protected value is never replaced or exposed.

Responses and errors

The promise resolves to a native Response for every response the approved upstream returned, including 4xx and 5xx. Check response.ok or response.status just as you would with fetch.

Canvas Drop policy and transport failures throw CanvasdropError instead. The stable codes include:

Code Status Meaning
CONNECTION_NOT_GRANTED 404 The profile is not attached to this canvas.
CONNECTION_DISABLED 503 An administrator disabled the profile.
CONNECTION_KEY_UNAVAILABLE 503 The external encryption key needed for protected headers is unavailable.
METHOD_NOT_ALLOWED 405 The method is unsupported or not approved for this profile.
DESTINATION_BLOCKED 403 Origin, DNS answer, redirect, or content encoding crossed the connection boundary.
REQUEST_TOO_LARGE 413 URL, headers, or body exceeded a request limit.
RESPONSE_TOO_LARGE 502 The upstream response exceeded the buffer limit.
CONNECTION_RATE_LIMIT 429 A connection-specific request bucket is spent.
CONNECTION_LIMIT 429 A per-canvas or process-wide in-flight limit is full.
UPSTREAM_TIMEOUT 504 The total DNS, request, redirect, and response deadline expired.
UPSTREAM_UNAVAILABLE 502 DNS or the approved upstream failed.

The normal canvas gates still run first: identity, canvas access, password, lifecycle, Public-link static-only, the broad runtime rate limit, and the Backend master switch. Detaching a grant, disabling its profile or Backend, revoking access, adding a password, expiring/archiving/disabling/deleting the canvas blocks the next request.

Fixed safety bounds

The first release is deliberately small and buffered:

Boundary Default
Relative URL 8 KiB
Caller headers 32 headers, 16 KiB total
Request body 256 KiB
Response body 2 MiB
Total deadline 10 seconds
Redirects 3, exact-origin only; DNS is revalidated at every hop
Rate 60/min per actor + canvas + profile; 600/min per profile
In flight 5 per canvas; 50 in the server process

The operator can set these read-only deployment values through the CANVAS_DROP_CONNECTIONS_* environment variables. Multi-process coordination is not part of this first release.

Security boundary

Canvas Drop accepts only HTTPS DNS origins—no IP literals, credentials, path, query, or fragment in the profile. Every DNS answer must be public. Mixed public/private answers are rejected, and the chosen public address is pinned to the TLS socket while the approved hostname remains the certificate, SNI, and Host name. Redirects must retain the exact origin and are re-resolved before the next socket is opened. Responses are identity-encoded, bounded, no-store, and expose only safe headers; cookies, redirects, CORS, server, and hop-by-hop headers are removed.

Protected header names are visible only to administrators as “configured”; the values are never returned by an API or loaded into the dashboard. Owners, editors, and list_canvas_connections over MCP see only the profile key, label, origin, allowed methods, and availability.

One residual trust boundary remains: the approved upstream receives the protected value and can deliberately reflect it in its own response body. Use a least-privilege credential scoped to that upstream and profile. Operators should also restrict the Canvas Drop server's network egress as defense in depth.

Public-link visitors can never use Connections. If an audience needs this backend, use Restricted or Whole org access and grant people or teams as needed.

Audience permission

A named Connection policy can override the canvas audience and restrict HTTP methods. In runtimePolicy.connections, set {audience: "viewers", methods: ["GET"]} for the intended profile key. Administrator grants and approved methods remain an upper bound. none denies invocation, including owners/editors; omit methods to use the administrator's allowed set, or use [] to deny every method. Read effective rights from me().resources.connections[key]. External item ownership must be enforced by the upstream service. See Permissions and defaults.

This feature defaults to owners and editors (connectionsAudience: "editors"). To allow signed-in viewers, an owner or editor selects All signed-in viewers under Connection access in the Backend tab, or sets connectionsAudience: "viewers" with set_capabilities / PATCH /api/canvases/{id}/capabilities. A disallowed caller receives 403 PERMISSION_DENIED (PermissionDeniedError) before a provider or upstream request starts. The setting leaves existing capability, public/static-only, quota and operator policy gates in place. Check me().permissions when rendering the feature's controls.