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 ?? [];
},
});