Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

HTTP

Xenocept exposes a low-level HTTP proxy for plugins that want to call external services:

POST /api/v1/deliver/http

This is not a “destination type” the user picks from a dropdown. It’s a primitive for plugin code — when a plugin wants to ship a session to a webhook, it formats the body itself and calls this endpoint.

Request / Response

The request body is application/json:

{
  "url": "https://example.com/intake",
  "method": "POST",
  "headers": {
    "Authorization": "Bearer...",
    "Content-Type": "application/json"
  },
  "body": "..." // verbatim string body
}

Supported methods: GET, POST, PUT, PATCH, DELETE.

The response:

{
  "status": 200,
  "body": "..."
}

The proxy returns the upstream’s status code and response body to the plugin. The plugin decides what counts as success.

What the Proxy Does (and Doesn’t Do)

The proxy is intentionally thin:

  • Sends one HTTP request
  • Returns the response
  • Does SSRF defense: rejects URLs that resolve to loopback, link-local, or unspecified ranges, or have a localhost-looking hostname
  • Returns 502 on transport failure

What it does not do:

  • No retries. One shot. If the plugin needs retries, it loops.
  • No multipart construction. Whatever string the plugin passes as body is sent verbatim. Plugins that need multipart build it themselves.
  • No ${VAR} env-var substitution in URL or headers. Plugins assemble the final strings before calling.
  • No HMAC signing of outgoing requests. If your downstream service requires a signature, the plugin computes it.
  • No streaming for huge payloads. The whole response is loaded into memory.

Why a Proxy?

Two reasons:

  1. SSRF safety. The frontend is a webview; if a plugin’s JavaScript could fetch arbitrary URLs from inside Xenocept, that would be an SSRF risk against any local services on the user’s machine. Forcing outbound HTTP through this proxy gives us one place to enforce a denylist.
  2. Auth wraps consistently. The deliver routes go through the same dev-unsafe-or-token middleware as the rest of the dangerous endpoints.

Plugin Usage Sketch

A destination plugin’s deliver typically looks like:

'use strict';

export async function deliver(session, attachments, config) {
  const body = JSON.stringify({
    session_id: session.id,
    text: session.comments.map((c) => c.text).join('\n'),
  });

  const r = await fetch('/api/v1/deliver/http', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      url: config.endpoint,
      method: 'POST',
      headers: { 'Authorization': `Bearer ${config.apiKey}` },
      body
    })
  });

  const result = await r.json;
  if (result.status >= 400)
    throw new Error(`destination returned ${result.status}: ${result.body}`);
}

See Plugin Delivery for the full plugin contract.