Skip to content

External API client

ExternalApiClient is the Workspace UI Project’s client for third-party HTTP APIs — exchange rates, geocoding, public product data, or any other service a page calls directly from the browser. It exists alongside the Moltaro API client so the two traffic kinds stay separated: workspace data goes through MoltaroApiClient, everything else goes through ExternalApiClient.

import { ExternalApiClient, ExternalApiClientError } from '@moltaro/workspace-ui'
const api = new ExternalApiClient()

ExternalApiClient never attaches the Moltaro bearer token or any workspace header to a request. It also defaults credentials to omit, so browser cookies are not sent either unless page code explicitly opts in via RequestInit.credentials. This is the entire point of using the client instead of raw fetch: a typo’d or attacker-controlled destination URL cannot receive the caller’s Moltaro session.

Workspace UI code is trusted workspace code compiled by Moltaro — the boundary here is governance and review, not a hostile-code sandbox. The client makes the safe behavior the default; it does not prevent a reviewer- approved page from deliberately sending credentials somewhere.

MethodReturnsNon-2xx behavior
request(input, options?)raw Responseresolves; check response.ok yourself
requestJson<T>(input, options?)parsed JSON as Tthrows ExternalApiClientError
requestText(input, options?)stringthrows ExternalApiClientError
requestBlob(input, options?)Blobthrows ExternalApiClientError

ExternalApiClientError carries the response status and statusText:

try {
return await api.requestJson<Payload>(url, { signal })
} catch (error) {
if (error instanceof ExternalApiClientError && error.status === 429) {
// back off and retry later
}
throw error
}

Moltaro imposes no destination allowlist. External requests are subject to the normal browser and network policy of the deployment: the destination must allow the WebApp origin via CORS, HTTPS pages cannot make mixed-content HTTP calls, and TLS validation applies as usual.

Requests made through the client are counted in Workspace UI runtime telemetry — status family and duration only, never URLs, headers, or bodies — and surface through Monitoring. Direct fetch, WebSocket, and EventSource remain available to page code, but calls made that way are not captured by Moltaro telemetry.

Confidential credentials — paid API keys, partner secrets, anything the workspace must not expose — belong in server-side C# business logic, where the key stays on the host and the page calls your command or function through MoltaroApiClient. Keys that are public by design, or supplied by the user at runtime for their own account, may be used in page code deliberately.

Worked example: Frankfurter exchange rates

Section titled “Worked example: Frankfurter exchange rates”

The UI Studio page generator ships an external-API example page built on the free Frankfurter service (api.frankfurter.dev, no key required). Its API module, adapted from the generated source:

import { ExternalApiClient } from '@moltaro/workspace-ui'
export interface ExchangeRate {
base: string
date: string
quote: string
rate: number
}
const api = new ExternalApiClient()
export async function loadExchangeRates (
base: string, quotes: string[], signal: AbortSignal) {
const query = new URLSearchParams({ base, quotes: quotes.join(',') })
const result = await api.requestJson<unknown>(
'https://api.frankfurter.dev/v2/rates?' + query.toString(),
{ signal, credentials: 'omit' })
if (!Array.isArray(result)) {
throw new TypeError('Frankfurter returned an invalid rates response.')
}
return result as ExchangeRate[]
}

The generated example validates the response shape before trusting it and never assumes an external API honors its own contract. Its Pinia store wires an AbortController into signal so a newer refresh cancels the in-flight one, and treats the resulting AbortError as silence rather than a failure — the same pattern described in Pages, components, and stores.