REST API Reference

Complete REST API reference for managing NoLag programmatically. API keys are project-scoped, so no organization or project IDs are needed in the URLs.

Base URL

https://api.nolag.app/v1

Authentication

All API requests require a project-scoped API key in the Authorization header:

Authorization: Bearer nlg_live_xxx.secret

API keys are created in the NoLag dashboard and are scoped to a specific project. The API key determines which project's resources you can access.

Apps

Apps are containers for rooms and topics within your project.

List Apps

GET /apps

# Query parameters (optional)
?page=1&limit=20&orderBy=name:ASC&name=chat&blueprintId=xxx&status=active

Returns a paginated list of apps in your project. Query parameters: page, limit, orderBy, name, blueprintId, status.

Create App

POST /apps

{
  "name": "My Chat App",
  "description": "A real-time chat application",
  "blueprintId": "optional-blueprint-id",
  "slug": "my-chat-app",
  "topics": ["messages", "typing"],
  "topicConfigs": {
    "messages": { "logging": true }
  }
}

Required fields: name. Optional fields: description, blueprintId, slug (auto-generated from name if omitted), topics (array of topic names), topicConfigs (per-topic logging and webhook settings).

The stored slug is never the slug you sent. NoLag appends four random hex characters to every app slug to keep it unique within the project, whether you supplied one or not. A request with "slug": "my-chat-app" is stored as something like my-chat-app-a3f9.

Read slug from the response and use that value everywhere afterwards, including setApp() and topic patterns. Room slugs behave differently: they are stored exactly as supplied.

Get App

GET /apps/{appId}

Update App

PATCH /apps/{appId}

{
  "name": "Updated Name",
  "description": "Updated description",
  "status": "active"
}

Updatable fields: name, description, status ("active" | "disabled"), config, files, topics, topicConfigs (including per-topic webhooks), pinnedBlueprintVersion, hydrationWebhook (deprecated), triggerWebhook (deprecated). Prefer configuring webhooks via topicConfigs.webhooks.

Delete App

DELETE /apps/{appId}

Returns 200 OK with { "success": true }.

Reset App to Blueprint

POST /apps/{appId}/reset-to-blueprint

Resets the app's configuration to match its source blueprint. The app must have been created from a blueprint. Returns the updated app.

Rooms

Rooms organize topics within an app. Each room has a unique slug used in topic patterns.

List Rooms

GET /apps/{appId}/rooms

# Query parameters (optional)
?name=general&slug=general&status=active&isStatic=true

Returns a plain array of rooms (not paginated). Query parameters: name, slug, status, isStatic.

Create Room

POST /apps/{appId}/rooms

{
  "name": "General Chat",
  "slug": "general",
  "description": "General discussion room",
  "topics": ["messages", "typing", "presence"],
  "metadata": {
    "maxUsers": 100
  }
}

Required fields: name. Optional fields: slug (auto-generated from name if omitted), description, topics, metadata (arbitrary JSON object).

Rooms must exist before a client uses them. The broker never creates rooms implicitly. Subscribing to a room that has not been created returns an unknown_topic error rather than delivering messages. Unlike app slugs, a room slug is stored exactly as you supply it.

Ensure Room (Idempotent)

POST /apps/{appId}/rooms/ensure

{
  "name": "Matter 4821",
  "slug": "matter-4821",
  "topics": ["messages"]
}

Create-if-not-exists, for runtime per-entity rooms such as one room per document, matter, or device id. Returns 200 OK with the existing room unchanged when the slug already matches, so it is safe to call on every entity creation.

Requires the app to have config.autoProvisionRooms set to true, and the number of auto-provisioned rooms is capped per app. Accepts the same body as Create Room.

Get Room

GET /apps/{appId}/rooms/{roomId}

Update Room

PATCH /apps/{appId}/rooms/{roomId}

{
  "name": "Updated Room Name",
  "description": "Updated description",
  "status": "active"
}

Updatable fields: name, description, status ("active" | "disabled"), topics, metadata. Note: slug cannot be changed after creation.

Delete Room

DELETE /apps/{appId}/rooms/{roomId}

Returns 204 No Content with an empty response body.

Note: Only dynamic rooms can be deleted. Static rooms defined in blueprints cannot be deleted.

Room Access

Room-level ACL grants control which actors the broker admits to a room. See Access Control for the model.

A room with no grants inherits the app's access mode, which is open by default. In practice that means every actor in the project can publish and subscribe until you add the first grant. Creating that first grant makes the room private, and from then on only actors with an explicit, unexpired grant are admitted. Adding one grant therefore removes access from everyone else, so provision all of a room's actors together.

Grant Actor Access

POST /apps/{appId}/rooms/{roomId}/actors

{
  "actorTokenId": "019fd987-a03a-70cf-9420-60538a186bbb",
  "permission": "pubSub",
  "topics": ["messages", "typing"],
  "expiresAt": "2026-12-31T23:59:59Z"
}

Required: permission, one of subscribe, publish, or pubSub, plus exactly one of actorTokenId (grant a single actor) or actorType (grant every actor of that type, for example "agent").

Optional: topics to limit the grant to named topics rather than the whole room, expiresAt (ISO 8601) for a time-limited grant, isActive (defaults to true), and role as a display label.

Returns 201 Created. A referenced actorTokenId must belong to the same project as the API key, otherwise the call returns 404.

List Room Grants

GET /apps/{appId}/rooms/{roomId}/actors

Returns a plain array of the room's grants.

Revoke Room Grant

DELETE /apps/{appId}/rooms/{roomId}/actors/{roomActorAccessId}

Returns 204 No Content. Removing the last grant returns the room to open access.

Actors

Actors represent clients that connect to NoLag (devices, users, services, or sessions). Each actor has an access token used for WebSocket connections.

Important: The access token is only returned when creating an actor. Save it immediately!

List Actors

GET /actors

# Query parameters (optional)
?name=web&actorType=device&status=active

Returns a plain array of actors (not paginated). Query parameters: name, actorType, status.

Create Actor

POST /actors

{
  "name": "Web Client",
  "actorType": "device",
  "metadata": {
    "platform": "web"
  }
}

Required fields: name, actorType. Optional fields: expiresAt (ISO 8601), metadata.

Actor types:

  • device - Browser, mobile app, IoT device
  • user - Authenticated user connection
  • service - Backend service, microservice
  • session - Browser session, temporary connection
  • agent - Autonomous LLM-powered connection
  • orchestrator - Coordination actor that dispatches work across agents
  • observer - Read-only audit or monitoring connection

Response:

{
  "actorTokenId": "019fd987-a03a-70cf-9420-60538a186bbb",
  "projectId": "019e8d1e-a59b-748b-9f69-dd03a91400f8",
  "accessScopeId": null,
  "keyId": "at_live_...",
  "name": "Web Client",
  "actorType": "device",
  "accessToken": "...",
  "status": "active",
  "expiresAt": null,
  "lastUsedAt": null,
  "metadata": null,
  "createdAt": "2026-08-07T00:03:48.000Z"
}

The actor's id field is actorTokenId. The {actorId} path parameter in the endpoints below takes this value. There is no field literally named actorId in any response, so read actorTokenId from the create or list response and use it in the URL.

keyId (at_live_...) is the actor's public identifier, used as the sub claim when minting client tokens. It is not the id for these endpoints.

Get Actor

GET /actors/{actorId}

Update Actor

PATCH /actors/{actorId}

{
  "name": "Updated Name",
  "status": "active",
  "metadata": {
    "platform": "mobile"
  }
}

Updatable fields: name, status ("active" | "disabled"), expiresAt, metadata.

Delete Actor

DELETE /actors/{actorId}

Returns 204 No Content with an empty response body.

Topic Patterns

Topics follow the pattern app-slug/room-slug/topic-name:

# Example topic patterns
my-chat-app/general/messages
my-chat-app/general/typing
my-chat-app/private-room/notifications

Response Format

Successful responses return the resource directly. The Apps list endpoint is paginated, wrapping results in a data array with a pagination object. Rooms and Actors list endpoints return plain arrays.

// Single resource (e.g. GET /apps/{appId})
{
  "appId": "01939f83-8b57-7c3e-a456-426614174000",
  "name": "My App",
  "description": "...",
  "status": "active",
  "createdAt": "2024-01-01T00:00:00Z"
}

// Paginated list (Apps only)
{
  "data": [...],
  "pagination": {
    "total": 100,
    "page": 1,
    "pageCount": 5
  }
}

// Plain array (Rooms and Actors)
[
  { "roomId": "...", "name": "General", ... },
  { "roomId": "...", "name": "Private", ... }
]

Error responses return a structured object with a unique ID for support reference:

{
  "id": "01939f83-8b57-7c3e-a456-426614174000",
  "message": "Description of the error",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

See Error Reference for details on HTTP status codes and error handling.

SDKs

For most use cases, we recommend using our official SDKs which wrap this API: