Standby mode
Run an Actor as a long-lived HTTP service.
Standby mode keeps an Actor running as an HTTP service instead of executing once and exiting. A standby Actor starts, listens on a port, and answers requests until the platform scales it down, so callers get a response without waiting for a container to boot.
Use standby when an Actor is called often with small inputs and latency matters. Use a normal run for batch crawls, where startup time is small next to the work itself.
Enabling standby
Set "usesStandbyMode": true in your Actor config (for example .actor/actor.json) and build the Actor, or turn standby on in the Actor's settings. Standby settings and their defaults:
| Setting | Default | Meaning |
|---|---|---|
idleTimeoutSecs | 300 | Extra standby runs with no requests for this long are stopped; the last one is stopped after twice this time |
desiredRequestsPerActorRun | 100 | A new run starts when the average load passes 80% of this |
maxRequestsPerActorRun | none | A run that has served this many requests is replaced |
memoryMbytes | 2048 | Memory per standby run |
build | latest | Build used for standby runs. Other users' standby runs use the User Build when it's set. |
Each caller gets their own standby runs; containers aren't shared between users. See the standby API reference for the settings API and how callers send requests.
Detecting standby
import { Actor } from 'scrapely';
await Actor.init();
if (Actor.isStandby()) {
startServer();
} else {
await runBatch();
await Actor.exit();
}Actor.isStandby() is true when ACTOR_META_ORIGIN (or APIFY_META_ORIGIN) is STANDBY, so one entrypoint can serve both modes.
Container port and URLs
| Value | How to read it | Meaning |
|---|---|---|
| Port | Actor.config.get('containerPort') | Port your server must listen on: ACTOR_WEB_SERVER_PORT, 4321 |
| Container URL | Actor.config.get('containerUrl') | ACTOR_WEB_SERVER_URL, the URL the platform proxies to this container |
| Standby URL | Actor.config.get('standbyUrl') | ACTOR_STANDBY_URL, set on standby runs |
| Idle timeout | process.env.ACTOR_STANDBY_IDLE_TIMEOUT_SECS | Seconds without requests before the run is stopped |
The SDK doesn't start a server for you. It only reports the port. Listen on it and bind to 0.0.0.0; a server bound to 127.0.0.1 isn't reachable from outside the container.
The platform considers a new standby run ready once GET / on the port returns any HTTP response, and waits up to 5 minutes for that. Answer / quickly, even before heavy setup finishes.
A standby Actor
import { createServer } from 'node:http';
import { Actor, log } from 'scrapely';
await Actor.init();
const port = Actor.config.get('containerPort');
const server = createServer(async (req, res) => {
const { pathname, searchParams } = new URL(req.url ?? '/', 'http://localhost');
if (pathname === '/') {
res.writeHead(200).end('ok');
return;
}
try {
const url = searchParams.get('url');
if (!url) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'url query parameter is required' }));
return;
}
const response = await fetch(url);
const html = await response.text();
const title = /<title>(.*?)<\/title>/is.exec(html)?.[1]?.trim() ?? null;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ url, title }));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: (error as Error).message }));
}
});
server.listen(port, '0.0.0.0', () => {
log.info(`Listening on ${port}`);
});This example never calls Actor.exit(). Exiting ends the run and defeats the purpose of standby; let the platform manage the lifetime.
With Express
import express from 'express';
import { Actor } from 'scrapely';
await Actor.init();
const app = express();
app.use(express.json());
app.get('/', (_req, res) => res.send('ok'));
app.post('/scrape', async (req, res) => {
const { url } = req.body;
if (!url) {
return res.status(400).json({ error: 'url is required' });
}
const response = await fetch(url);
res.json({ url, status: response.status });
});
app.listen(Actor.config.get('containerPort'), '0.0.0.0');Add express to your package.json dependencies.
Writing to storage from standby
A standby run can use datasets and key-value stores, but every request shares the run's same default storages for the whole lifetime of the container. A default dataset collects rows from many requests.
When each request needs its own output, return the result in the HTTP response, or open a named storage per request:
const dataset = await Actor.openDataset(`results-${requestId}`);
await dataset.pushData(items);Handling shutdown
When the platform scales a standby run down (idle timeout or maxRequestsPerActorRun), it marks the run ABORTING and stops the container. The process receives SIGTERM and is killed about 5 seconds later; no aborting event is sent. The SDK's own SIGTERM handler flushes storage and charges within those 5 seconds and exits.
Stop accepting connections as soon as SIGTERM arrives:
process.on('SIGTERM', () => {
server.close(() => log.info('Server closed'));
});A graceful abort through the API sends aborting first and allows 30 seconds; with the default graceful shutdown, the SDK then calls Actor.exit().
Sharing state across requests
Module-level variables persist between requests in the same container, which makes them useful as a cache. But a container can be replaced at any time and several containers may serve traffic at once. Treat in-memory state as a cache, never as the source of truth. Anything that must survive belongs in a key-value store, for example through Actor.useState().
Checklist
- Set
usesStandbyModeor enable standby in the Actor settings - Check
Actor.isStandby()to choose behavior at startup - Listen on
Actor.config.get('containerPort'), bound to0.0.0.0 - Answer
GET /quickly so the run becomes ready - Don't call
Actor.exit()on the request path - Return request-specific results in the response
- Close the server on
SIGTERM; you have about 5 seconds - Keep per-request state out of module scope
What's next
- Standby API reference: invoking a standby Actor over HTTP
- Environment: container metadata and configuration
- API reference