Skip to main content

External APIs

Handler-based action() functions can call external HTTP APIs using ctx.fetch(). All outbound requests are restricted to an explicit allowlist declared in lumio.config.json.

Declaring the egress allowlist

Add an egress section to lumio.config.json:

{
"egress": {
"allowHosts": [
"site.api.espn.com",
"api.openweathermap.org",
"*.scdn.co"
]
}
}

Only listed hosts are reachable from ctx.fetch(). Requests to unlisted hosts are rejected before the network layer.

Using ctx.fetch

ctx.fetch has the same interface as the standard fetch() API:

import { action, v } from "@zaflun/lumio-sdk/server";

export const getWeather = action({
args: { city: v.string() },
handler: async (ctx, args) => {
const apiKey = ctx.secrets.get("OPENWEATHER_API_KEY");

const res = await ctx.fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(args.city)}&appid=${apiKey}&units=metric`
);

if (!res.ok) {
throw new Error(`Weather API error: ${res.status} ${res.statusText}`);
}

const data = await res.json();
return {
temperature: data.main.temp,
description: data.weather[0].description,
city: data.name,
};
},
});

Wildcard support

Wildcards (*) match any subdomain at that level only:

PatternMatchesDoes not match
*.espn.comapi.espn.com, site.espn.comespn.com, a.b.espn.com
*.scdn.coi.scdn.co, p.scdn.coscdn.co
api.example.comapi.example.com exactlysub.api.example.com

Security constraints

RuleDescription
HTTPS onlyAll ctx.fetch() requests must use https:// — HTTP is blocked
No private IPsRequests to RFC 1918 ranges (10.x, 172.16.x, 192.168.x) and localhost are blocked
Allowlist requiredA ctx.fetch() to any host not in allowHosts throws immediately
10 requests/invocationMaximum 10 ctx.fetch() calls per action invocation
30-second timeoutEach request times out after 30 seconds

Setting ports

By default, only port 443 (HTTPS) is allowed. To use a non-standard port, declare it explicitly:

{
"egress": {
"allowHosts": ["my-server.example.com"],
"allowPorts": [8443, 9443]
}
}

Review process

The egress allowlist is reviewed during the admin approval process. Reviewers check:

  • Are the listed hosts appropriate for the extension's stated purpose?
  • Are wildcards too broad?
  • Are the hosts known, reputable services?

Requests to social engineering domains, telemetry/tracking hosts, or hosts unrelated to the extension's function will result in rejection.

Example: Sports scoreboard

{
"extensionId": "...",
"name": "Sports Scoreboard",
"server": true,
"egress": {
"allowHosts": [
"site.api.espn.com",
"cdn.espn.com"
]
}
}
// server/functions.ts
export const getScoreboard = action({
args: { sport: v.string(), league: v.string() },
handler: async (ctx, args) => {
const res = await ctx.fetch(
`https://site.api.espn.com/apis/site/v2/sports/${args.sport}/${args.league}/scoreboard`
);
const data = await res.json();
return data.events ?? [];
},
});