Network and credentials API
Apps have no network access of their own. client.network lets the user configure one endpoint per named slot, keeps the API key outside your app, and posts JSON to exactly that endpoint.
Availability
Requires the system.network permission and the native desktop — the browser preview has no credential store, so the service reports unavailable there. No workspace connection is needed: this is about the internet, not the host you are connected to.
Interface
interface AppConnection {
endpoint: string; // what the user configured
revision: string; // pin this when sending a request
hasKey: boolean; // whether a key is stored — never the key itself
remembered: boolean; // saved in the OS credential store, or session-only
}
interface AppHTTPResponse {
status: number;
contentType: string;
read(signal?: AbortSignal): Promise<Uint8Array | null>; // null ends the body
close(): Promise<void>;
}
interface AppNetworkAPI {
profile(slot: string, signal?: AbortSignal): Promise<AppConnection | null>;
configure(
options: { slot: string; suggestedEndpoint?: string },
signal?: AbortSignal,
): Promise<AppConnection | null>;
forget(slot: string, signal?: AbortSignal): Promise<void>;
postJSON(
options: { slot: string; revision: string; body: Json },
signal?: AbortSignal,
): Promise<AppHTTPResponse>;
}A slot is your own name for a connection — "model", "webhook" — matching ^[a-zA-Z0-9_-]{1,64}$. One app can hold several.
configure() opens the desktop's trusted connection dialog. The user enters the endpoint and, optionally, the key; the key goes to the operating system's credential store (Windows Credential Manager, macOS Keychain, Linux Secret Service) or is kept for the session only. Your app receives the endpoint, a revision and the two booleans, and never the secret. The dialog resolves to null if the user dismisses it.
Endpoints must be complete http:// or https:// URLs of at most 2048 characters, with no embedded credentials, query string or fragment. Requests are POSTs with a JSON body to exactly that URL: no other method, no extra headers, no redirects followed.
Examples
Configure on demand, then send a request:
async function connection(): Promise<AppConnection> {
const existing = await client.network.profile("model");
if (existing?.hasKey) return existing;
const configured = await client.network.configure({
slot: "model",
suggestedEndpoint: "https://api.example.com/v1/messages",
});
if (!configured) throw new Error("No endpoint configured.");
return configured;
}
async function ask(question: string): Promise<string> {
const { revision } = await connection();
const response = await client.network.postJSON({
slot: "model",
revision,
body: { prompt: question },
});
try {
if (response.status >= 400)
throw new Error(`Endpoint returned ${response.status}`);
const decoder = new TextDecoder();
let text = "";
for (;;) {
const chunk = await response.read();
if (chunk === null) break;
text += decoder.decode(chunk, { stream: true });
}
return text;
} finally {
await response.close();
}
}The body arrives in chunks of at most 32 KiB, so a streaming endpoint can be consumed as it arrives. Always close() in a finally.
Let users disconnect:
await client.network.forget("model"); // removes the stored key and endpoint for this slotShow status honestly in your interface: hasKey tells you whether a key is stored, and remembered whether it survives a restart.
Errors
| Code | Message | Meaning |
|---|---|---|
invalid | Invalid connection slot. | The slot name breaks the pattern. |
invalid | JSON request exceeds 3 MiB. | Body too large. |
invalid | Duplicate HTTP request. | A request with that identity is already running. |
busy | Close an existing HTTP request first. | More than four concurrent requests. |
busy | Wait for the current response read. | Two reads on one response. |
busy | Finish the open connection dialog first. | configure() called twice. |
closed | HTTP response closed. | Used after close(). |
aborted | HTTP request canceled. / Connection setup canceled. | A signal fired, or the user cancelled. |
Errors raised by the desktop while saving a credential are shown in the dialog rather than to your app — for example when the OS credential store is locked, or when an endpoint changes and the key must be entered again.
Lifecycle and permissions
Connections belong to the installation of your app, like storage: remembered connections survive updates and restarts; session-only connections do not survive restarting the desktop. Removal retires the installation identity, so reinstalling cannot access its old connections. Removal does not itself delete the old credential-store entries; use forget(slot) before removal to delete a saved connection. Every window of your app sees the same slots.
The revision pins the endpoint and credential version that a request uses. If the user reconfigures the slot, old revisions stop working — call profile() again and use the fresh revision rather than caching one indefinitely.
system.network grants nothing else: it does not give access to the connected host's files or console, and it cannot reach arbitrary URLs. Responses are capped at 16 MiB.
Related guides
- Canvas Assistant — an app that uses this API for a model endpoint.
- Storage API — local state, and why secrets do not belong there.
- Extension permissions and trust — how this permission is presented.
- App lifecycle and services — cleanup and error model.