API
The EffectStream Node runs a powerful, built-in HTTP server that exposes a RESTful API. This API is the primary way for your Frontend (dApp) and other external services to read the real-time, aggregated state of your application.
You can explore the API interactively via the OpenAPI (Swagger) interface, typically available at http://localhost:PORT/documentation in your local development environment.
Built-in API Endpoints
Your EffectStream node comes with a set of default endpoints to query system status and generic data structures.
| Path | Description |
|---|---|
/health | A simple health check to see if the node is running. Returns {"status": "ok"}. |
/block-heights | Shows the current sync progress for all connected chains. |
/addresses | Returns a paginated list of all wallets and their associated EffectStream accounts. |
/scheduled-data | Shows a paginated list of all pending scheduled inputs (timers/ticks). |
/tables/:tableName | A powerful generic endpoint to query any public table in your database. It supports automatic keyset pagination for high performance. |
/primitives/:primitiveName | Similar to /tables, but specifically for querying the data aggregated by a primitive (e.g., all ERC721 token owners). |
/rpc/evm | A partial implementation of the Ethereum JSON-RPC specification, allowing you to use standard EVM tools like viem to query the EffectStream L2 state. |
/grammar | Returns the JSON representation of the grammar your state machine is using. |
/tables | Lists the queryable public tables, without reading any rows. |
/table-schema/:tableName | Returns the column schema for one public table. |
/primitives-schema/:primitiveName | Returns the schema for one primitive's aggregated data. |
/sync-protocols/:protocolName/blocks | Returns block data for a single named sync protocol. |
/db_status | Reports database connection status. Useful as a readiness probe alongside /health. |
Seven of these sit behind an API-key check: /addresses, /scheduled-data, /tables, /tables/:tableName, /table-schema/:tableName, /primitives/:primitiveName and /primitives-schema/:primitiveName. Send the key configured in API_KEY_OPEN_ENDPOINTS_EXPLORER; note that it ships with a default value you should change before exposing a node publicly.
Development and debug endpoints
These are registered only when ENABLE_DEV_AND_DEBUG_ENDPOINTS is set. Without it they are absent entirely and return 404 rather than an authorization error. Keep it unset in production.
| Path | Description |
|---|---|
/debug/sync-protocols | Detailed per-protocol sync diagnostics. |
/debug/metrics | Runtime metrics for debugging. |
/config | Dumps the resolved node configuration. |
/db_acquire_lock | Manually acquires the startup database mutex. |
/db_release_lock | Releases a stuck database mutex — useful after an unclean shutdown blocks startup. |
See Environment Variables for the flag, and the batcher documentation for the equivalent gated batcher routes (/force-batch, /clear-inputs).
Creating Custom API Endpoints
While the built-in endpoints are useful, you will often need to create custom routes that expose specific, aggregated data tailored to your application's needs. EffectStream makes this easy by allowing you to define a custom apiRouter function.
This function is passed a fastify server instance, allowing you to register your own GET, POST, or other routes.
Example (api.ts):
import { type Static, Type } from "@sinclair/typebox";
import { runPreparedQuery } from "@effectstream/db";
import { getMyCustomGameData } from "@example-evm-midnight/database"; // Your custom pgtyped query
import type { Pool } from "pg";
import type { StartConfigApiRouter } from "@effectstream/runtime";
import type fastify from "fastify";
// Define the schema for the API response using TypeBox.
// This provides automatic validation and generates OpenAPI documentation.
const ResponseSchema = Type.Array(Type.Object({
token_id: Type.String(),
owner: Type.Union([Type.Null(), Type.String()]),
property_name: Type.String(),
value: Type.String(),
}));
/**
* Register your custom API endpoints here.
* @param server - The Fastify instance provided by the EffectStream runtime.
* @param dbConn - A database connection pool.
*/
export const apiRouter: StartConfigApiRouter = async function (
server: fastify.FastifyInstance,
dbConn: Pool,
): Promise<void> {
server.get<{
Reply: Static<typeof ResponseSchema>;
}>("/api/my-game/get-characters", async (request, reply) => {
// Execute a pre-defined, type-safe database query
const result = await runPreparedQuery(
getMyCustomGameData.run(undefined, dbConn),
"/api/my-game/get-characters",
);
reply.send(result);
});
};
By defining custom endpoints, you can create an efficient, tailored API that provides your frontend with exactly the data it needs, in the format it expects, without exposing the raw database structure.
To define and read the database queries see the database section