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 Paima 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 Paima L2 state. |
/grammar | Returns the JSON representation of the grammar your state machine is using. |
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 Paima 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