Theia API Documentation v1.76.0
    Preparing search index...

    Module @theia/external-api


    theia-ext-logo

    ECLIPSE THEIA - EXTERNAL API EXTENSION


    This package serves HTTP APIs intended for consumption by external tools (control planes, dashboards, CLIs, scrapers), either on a dedicated HTTP server or on Theia's main HTTP server.

    Serving external APIs on their own port keeps them independent of the main server's frontend-oriented protections: in Electron deployments the main port is sealed by the Electron security token (making endpoints on it unreachable for external processes), while in browser deployments it is open. The dedicated external API server behaves identically on both platforms and has its own explicit, preference-controlled protection.

    The external API is configured through preferences. They configure the backend and therefore apply per user: their maximum scope is the user scope, so they cannot be overridden in workspace settings.

    • externalApi.delivery: Whether and how the external API is served:
      • off (default): The external API is not served.
      • samePort: Serve on the same port as the backend. Note that in Electron deployments the main port requires the Electron security token, so the external API is not reachable for external processes in this mode.
      • separatePort: Serve on a dedicated port, configured via externalApi.port.
    • externalApi.port: Port on which the external HTTP API is served. Only used with separatePort delivery.
    • externalApi.hostname: Hostname or IP address the dedicated server binds to. Defaults to localhost; use 0.0.0.0 to accept remote connections. A name is bound as it resolves, so clients using the other loopback address may not reach a server bound to localhost; use 127.0.0.1 or ::1 to pin one. Only used with separatePort delivery.
    • externalApi.token: Bearer token required to access protected external API endpoints (Authorization: Bearer <token>). When empty (default), the external API is served without verification.

    The backend applies preference changes immediately: the server starts, restarts, moves between delivery modes, or stops without requiring a restart.

    The configuration applies to the backend as a whole. When several frontends are connected, the configuration pushed last wins. In practice the frontends of one backend share the user preferences and push the same values. The configuration stays in effect until it is changed or the backend stops; it is not revoked when the frontend that pushed it disconnects.

    Extensions contribute endpoints by binding an ExternalApiContribution in their backend module. A contribution declares the absolute path it is mounted on (no path conventions are imposed, but paths of different contributions must not nest, as each contribution answers all requests below its path) and registers its routes on the ExternalApiRouter passed to configure. With samePort delivery the path also must not nest with one of Theia's own routes, as it would shadow them:

    @injectable()
    export class MyExternalApi implements ExternalApiContribution {
    readonly path = '/api/my-feature';

    configure(router: ExternalApiRouter): void {
    router.get('/', () => RestResult.ok({ ok: true }));
    router.get('/items/:id', ({ params }) => {
    const item = this.service.find(params.id);
    return item ? RestResult.ok(item) : RestResult.notFound();
    });
    router.post('/items', { bodySchema: CreateItemRequest.SCHEMA }, async ({ body }) =>
    RestResult.created(await this.service.create(body)));
    }
    }

    bind(MyExternalApi).toSelf().inSingletonScope();
    bind(ExternalApiContribution).toService(MyExternalApi);

    Typed routes take care of the recurring endpoint mechanics so that all contributions of the external API behave consistently:

    • Request bodies are parsed as JSON and validated against the JSON Schema declared in the route options, without invoking the handler on failure: schema violations are rejected with 400 { "error": "invalid request", "details": [...] } carrying the validation messages, malformed JSON with 400 { "error": "invalid request" }, and bodies exceeding the size limit (default 1mb, configurable per route via jsonLimit) with 413 { "error": "payload too large" }. Unlike the stable error codes, details are human-readable and make no stability promise. Clients must send the body with a Content-Type: application/json header; without it the body is not parsed and the request is rejected as if it were empty.
    • Handlers return a RestResult (RestResult.ok, created, accepted, noContent, badRequest, notFound, conflict, ...) that is written to the response, so that success and error responses share one wire format (errors as { "error": "<code>" }).
    • Errors thrown by handlers are logged and answered with 500 { "error": "internal error" }.
    • Requests to paths no route matches are answered with 404 { "error": "not found" } instead of an HTML error page.

    Server-sent event streams are served through eventStream, which manages the connected clients, keep-alive comments, and coalesced broadcasts:

    configure(router: ExternalApiRouter): void {
    const stream = router.eventStream('/events', {
    event: 'items',
    snapshot: () => this.service.getItems()
    });
    router.toDispose.push(this.service.onDidChangeItems(() => stream.notifyChanged()));
    }

    configure is called whenever the routing is rebuilt, i.e. when the external API configuration changes. The router of the previous build is disposed beforehand: event streams are closed automatically, so clients reconnect against the new configuration, and contributions register their own build-scoped resources, such as the event listener above, in the router's toDispose collection.

    For anything the typed routes do not cover, router.raw exposes the underlying express router mounted at the contribution's path (behind the token verification): existing express routers and middlewares can be mounted there unchanged, keeping their own request handling and response format. Errors they do not handle themselves are still reduced to the uniform error format: server errors are answered with 500 { "error": "internal error" }, client errors keep their status with the HTTP status text as the error code, except for plain 400s, which keep the established invalid request code.

    The wire format is written by the ExternalApiResponseWriter; rebinding it changes the format of all typed routes, validation failures, the token verification, and the fallback error handling consistently.

    The external API describes itself: the typed routes and event streams of all contributions are recorded and served as an OpenAPI 3.1 document at GET /api/openapi.json. The endpoint is served without token verification, but scopes the document to the requester: when a token is configured, requests without it receive a document covering only the unprotected contributions, while requests carrying the token receive the full document. Undocumented routes appear with their method, path, and body schema only; the optional route documentation makes the document useful to external consumers, for example for generated clients, documentation UIs, or MCP tool definitions:

    router.post('/items/query', {
    operationId: 'queryItems',
    summary: 'Query items by creation time range.',
    bodySchema: ItemQuery.SCHEMA,
    validate: body => body.from <= body.to ? undefined : 'from must not be after to',
    responses: {
    200: { description: 'The matching items.', schema: ITEM_LIST_SCHEMA }
    }
    }, async ({ body }) => RestResult.ok(await this.service.query(body)));
    • bodySchema is a RestBodySchema<B>, a JSON Schema carrying the TypeScript type of the bodies it accepts. A schema constant is declared once, next to the body's interface, and is the single place asserting that schema and type agree; the type flows into the route's handler and validate function.
    • validate covers constraints JSON Schema cannot express, such as cross-field dependencies like the range check above, and runs on the schema-valid body: it returns undefined (or an empty string) for valid bodies, otherwise the message the request is rejected with. Prefer expressing constraints in the schema (e.g. non-blank strings as "pattern": "\\S") so that they are visible to consumers of the OpenAPI document.
    • A contribution's documentation groups its routes under an OpenAPI tag; event streams are documented through their options (summary, dataSchema, ...).
    • Routes registered on router.raw are not recorded and do not appear in the document.

    The document is assembled by the OpenApiDocumentBuilder; rebind it to customize the document. When a token is configured, the full document declares a bearerAuth security scheme on all protected operations.

    When a token is configured, contributions are protected by bearer token verification; requests failing it are answered with 401 { "error": "unauthorized" }. A contribution with its own authentication scheme (e.g. OAuth) or one that is conventionally public can opt out by declaring unprotected = true.

    • The token is stored in plain text in the user settings and transmitted as a bearer header. Use it to keep casual local processes out, not as a substitute for network-level security; prefer binding to localhost and use TLS-terminating reverse proxies for remote scenarios.
    • Without a token, anyone who can reach the configured port can call all contributed endpoints; the backend logs a warning when serving without a token on a non-local hostname.
    • The external API is for external tools, not for browser pages: requests carrying an Origin header are rejected with 403 { "error": "forbidden" }. Browsers execute CORS-simple cross-origin requests before any preflight, so a malicious web page could otherwise drive state-changing endpoints, most notably with samePort delivery. External tools do not send an Origin header and are unaffected.

    "Theia" is a trademark of the Eclipse Foundation https://www.eclipse.org/theia

    Modules

    browser
    browser/external-api-frontend-contribution
    browser/external-api-frontend-module
    common
    common/external-api-configuration
    common/external-api-preferences
    common/rest-body-schema
    node
    node/external-api-backend-module
    node/external-api-contribution
    node/external-api-event-stream
    node/external-api-response-writer
    node/external-api-router
    node/external-api-server
    node/openapi-document-builder
    node/openapi-spec-contribution
    node/rest-result