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.
REQUEST_TIMEOUT 408 A public request body did not arrive within the connection deadline.
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.
CONNECTION_DAILY_LIMIT 429 The public grant's shared daily request cap is spent. Resets at midnight UTC; do not retry immediately.
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: canvas access, password, lifecycle, the public grant when required, 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, availability, and any public policy.

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 connections (opt-in)

Public visitors cannot use a connection by default. An administrator can enable Public access on one canvas grant in Admin → Connections → Manage. This requires exact endpoint paths, a subset of the profile's methods, and a daily request cap. This is an exception for that named connection only: anonymous visitors still cannot use me(), KV, files, AI, realtime, or authoring.

The canvas must have an active public link, Backend on, and the named Connection audience set to viewers (or inherit the viewers audience). Passwords, expiry, the instance public-link switch, owner publishing permissions, and profile revocation still apply. Turning public access off blocks the next request.

For public calls, query strings, encoded paths and redirects are refused. Only the exact configured paths are forwarded. The existing body, response, egress, rate and concurrency limits also apply. Public rate buckets use the server-resolved client IP; logs retain a per-day, per-canvas keyed hash rather than the address. The shared daily counter is database-atomic, survives restarts and policy edits, and counts admitted attempts, including unfinished uploads and upstream failures. Public body reads have the same configured deadline as upstream calls. It is a request cap, not a monetary budget. Public callers can submit any body the approved endpoint accepts, so approve only endpoints and credentials suitable for public use.

The administrator API is PUT /api/admin/connections/:id/canvases/:canvasId/public with {"policy":{"paths":["/v1/analyze"],"methods":["POST"],"requestsPerDay":2000}}. Use {"policy":null} to disable it. Owners can read the policy through the existing manager API and list_canvas_connections MCP tool, but cannot enable it.

Check availability without requesting the visitor's identity:

const access = await canvasdrop.connections.status("analyzer");
// { invoke: true, methods: ["POST"], publicAccess: true }
if (access.invoke && access.methods.includes("POST")) {
  // Show the analysis action. Calls can still fail if limits or policy change.
}

status uses GET /v1/c/:slug/connection-status/:profile, returns only those three fields, and is never cached. publicAccess means this caller uses the public grant; signed-in owners and invited participants retain their existing rights. When adding status to an existing canvas, version the SDK script URL (for example /sdk/v1.js?release=public-connections-1) so returning visitors get it.

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 connections.status(key), or me().resources.connections[key] for signed-in users. 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.