External APIs
A plugin can call external HTTP APIs in two ways:
- Requests through the platform — the plugin calls
hostApi.externalApiCall(), and the platform proxy executes the request from its backend. This is the primary method: the platform authorizes the request, runs audits, applies rate limits, and blocks unsafe destinations. - Direct requests — the plugin sends the request from the browser using
fetchorXMLHttpRequest. The platform isn't involved in such requests, so they don't support authorization and aren't recommended.
The access method for each domain is set in the manifest.
Manifest permission
Add the permissions.external section to manifest.json with a list of allowed domains and the access methods for them:
{
"permissions": {
"external": [
{
"domain": "api.example.com",
"access": ["proxy"],
"authorization": {
"type": "oauth",
"scopes": ["read", "write"],
"contextTypes": ["user"]
},
"instructions": {
"ru": "Создайте токен с правами read и write в настройках Example API",
"en": "Create a token with the read and write permissions in Example API settings"
}
}
]
}
}
In the domain field, specify the hostname without the protocol, path, or port. Declare each domain only once.
The access field sets the access method for the domain:
proxy— requests through the platform usinghostApi.externalApiCall();direct— direct requests from the browser. The platform adds the domain to theconnect-srcdirective of the plugin's CSP.
If the access field isn't specified, both methods are allowed. Specify access explicitly and allow only the method the plugin uses — usually ["proxy"]. A domain with access: ["proxy"] isn't added to the CSP, so the browser will block direct requests to it.
The authorization field is optional — you can omit it for domains that don't require authorization.
Proxy authorization
The authorization field describes which token a domain requires and how to insert it into the request, but does not contain the token itself:
type—oauthfor an OAuth token ortokenfor an API key and other non-OAuth tokens;scopes— the minimum token permissions the plugin needs. The field only declares the permissions and doesn't grant them to the token;contextTypes— the token storage context. Always specify["user"]: only the user's personal tokens are supported.
In the instructions field, tell the user where to get a token and which permissions to grant it, in Russian and English.
Authorization takes place on the Tracker side:
- The plugin calls
hostApi.externalApiAuthCheckAndRequest()with theusercontext. - If the user doesn't have a token for the domain yet, Tracker shows a token entry dialog with the instructions from the manifest.
- Tracker saves the token for the current user and domain. The plugin only receives the
{ success: boolean }result — the token itself never reaches the plugin. - When
hostApi.externalApiCall()is called, the platform inserts the token into the request and executes it from its backend.
If the token stops working, for example, expires, the user needs to enter a new token.
Requests through the platform
This is the primary way to call external APIs. The plugin passes the request parameters — URL, method, headers, and body — to hostApi.externalApiCall(), and the platform executes the request from its backend and returns the result. The platform:
- Inserts a token into the request if the
authorizationfield is configured for the domain. The token isn't passed to the plugin's iframe. - Blocks requests to unsafe destinations: private networks,
localhost, and unauthorized redirects. - Runs request audits.
- Applies rate limits per plugin and organization.
To send requests to a domain through the platform, specify access: ["proxy"] for it in the manifest.
Basic usage
A typical scenario — check authorization, request it from the user if needed, then call the API:
import {
hostApi,
PluginActionError,
EXTERNAL_API_CALL_ERROR,
} from "@weavix/tracker-plugin-sdk-react";
async function fetchItems() {
// Check authorization and show the dialog if needed
const { success } = await hostApi.externalApiAuthCheckAndRequest({
domains: ["api.example.com"],
contextType: "user",
});
if (!success) {
// The user closed the dialog or the timeout expired
return;
}
// Call the external API through the host's proxy
const { status, body } = await hostApi.externalApiCall({
url: "https://api.example.com/v1/items",
method: "GET",
contextType: "user",
});
// status — HTTP response status
// body — response body (Record<string, unknown>)
}
POST with a body and timeout:
const { status, body } = await hostApi.externalApiCall({
url: "https://api.example.com/v1/items",
method: "POST",
headers: { "Content-Type": "application/json" },
body: { name: "New item", value: 42 },
contextType: "user",
timeoutMs: 10000,
});
Error handling
On a network error or an error response status, the host throws a PluginActionError with the code EXTERNAL_API_CALL_ERROR (1013). Details are in e.errorData:
try {
await hostApi.externalApiCall({
url: "https://api.example.com/data",
method: "GET",
});
} catch (e) {
if (e instanceof PluginActionError && e.code === EXTERNAL_API_CALL_ERROR) {
console.error("Proxy error:", e.errorData);
}
}
Managing authorization
The quick way — externalApiAuthCheckAndRequest checks authorization and, if needed, shows the dialog in a single call; see the example above.
Manual control. If you need finer-grained control, use the individual methods:
// Check the status for domains
const { domains } = await hostApi.externalApiAuthGetStatus({
domains: ["api.example.com"],
contextType: "user",
});
const isAuthed = domains.every((d) => d.authenticated);
// Show the authorization dialog with a hint
if (!isAuthed) {
await hostApi.externalApiAuthRequest({
domains: [
{
domain: "api.example.com",
instructions: {
ru: "Войдите в Example",
en: "Sign in to Example",
},
},
],
});
}
// Revoke saved credentials
await hostApi.externalApiAuthRevoke({
domains: ["api.example.com"],
contextType: "user",
});
For more details on the methods, see hostApi.externalApi*.
Direct requests
Warning
Direct requests don't support authorization and aren't recommended. Use requests through the platform instead.
If the domain's access field includes direct, the plugin can send requests to it from the browser using fetch or XMLHttpRequest:
{
"permissions": {
"external": [
{
"domain": "cdn.example.com",
"access": ["direct"]
}
]
}
}
const response = await fetch("https://cdn.example.com/data.json");
const data = await response.json();
The platform only adds the domain to the connect-src directive of the plugin's CSP and isn't involved in the request:
- It doesn't insert tokens: you can't specify the
authorizationfield for such a domain. - It doesn't block unsafe destinations, run audits, or apply rate limits.
The external service must support CORS for the plugin's origin. The direct method only extends the connect-src directive: loading images, scripts, and styles from the domain is still blocked.